Un diagramma con matplotlib

Qualche tempo fa avevo usato Scilab per creare grafici rappresentanti l’andamento di quantità variabili nel tempo. Scilab è un progetto (ai miei tempi si sarebbe detto un sistema) molto potente ma a dirla tutta non mi ha soddisfatto pienamente. Anche perché c’è matplotlib, che fa esattamente quello che mi serve.

Recentemente ho avuto un paio di disavventure informatiche, forse tra loro collegate: è bastato un temporale con lampi e tuoni (era una notte buia e tempestosa) per privarmi del collegamento con il resto del mondo, o quasi, per 15 giorni.
Comunque eccomi qua, pronto a ripartire.

Iniziare con matplotlib è facilissimo: basta aprire il manuale in pdf Matplotlib.pdf e copiare gli esempi che trovate al cap. 3 Pyplot Tutorial, p.9. Quindi chi si fosse spaventato delle dimensioni del manuale, 909 pagine per un totale di 8.8 MB, avrebbe sprecato un’ottima occasione per preoccuparsi. Nel manuale c’è tutto, anzi di più, compreso quello che non serve (almeno immediatamente). Io non l’ho letto tutto, ci mancherebbe. Molti capitoli sono per moduli estremamente particolari. È peraltro fatto molto bene, ci sono i collegamenti interni utilissimi.

Allora ecco subito quello che ho ottenuto

La linea rossa è la rappresentazione delle prime cifre decimali di π, la blu quelle di e e la verde i primi termini della successione di Fibonacci; siccome questi sarebbero subito troppo grandi rispetto agli altri dati li ho diviso per 10. Come si può notare i valori dell’asse x partono da 100 per la prima e la terza serie e da 105 per la seconda.
Bello vero? Dai dite di sì che ci tengo ;-)

Ma se si vogliono plottare altri dati, non dico che π, e e Fib siano interessanti ma c’è anche altro lì fuori! Cerrrto, lo script è fatto per qualunque serie ragionevole di dati, legge un file di dati.
Penso che la cosa migliore sia di presentare lo script, eccolo

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import numpy as np
import matplotlib.pyplot as plt

def isfloat(st):
	if st[0] == '-' :
		st = st[1:]
	p = st.find('.')
	if p >= 0:
		st = '%s%s' %(st[:p], st[p+1:])
	return st.isdigit()

def dassex(dx, nval):
	xdata = []
	n = len(dx)
	if n == 2: # start, passo
		xstart = dx[0]
		passo = dx[1]
		for c in range(0, nval):
			xdata.append(xstart + passo * c)
	elif n != nval:
		print "la serie dell'asse X dovrebbe avere", nval, "valori, sono", n
		sys.exit(2)
	else:
		xdata = dx
	return xdata

klist = ['n', 'c', 'y', 'x', '|.']
xmin = xmax = ymin = ymax = ''
gridp = False

if len(sys.argv) > 1:
	dname = sys.argv[1]
else:
	dname = 'dati'
fd = open(dname, 'r')
dati = fd.read()
fd.close()
dati = dati.strip()

p = dati.find('\n')
while p > 0:
	dati = '%s %s' %(dati[:p], dati[p+1:])
	p = dati.find('\n')

p = dati.find('\t')
while p > 0:
	dati = '%s %s' %(dati[:p], dati[p+1:])
	p = dati.find('\t')

words = dati.split(' ')
cont = 0
while cont < len(words):
	if words[cont] == '':
		words.remove('')
	cont += 1

nw = len(words)
namep = 'uno'
cont = 0
titolo = ''

dentro = False
while not dentro and cont < nw:
	word = words[cont]
	if word == '.|':				# .|
		dentro = True
		colore = 'red'
		ydata = []
		inY = False
		inX = False
		dx = [0, 1] #start, passo
	elif word == '!g':				# !g
		gridp = True
	elif word == 't':				# t
		cont += 1
		titolo = words[cont]
		t = titolo.split('_')
		titolo = ' '.join(t)
	elif word == 'ix':				# ix
		cont += 1
		xmin = float(words[cont])
		cont += 1
		xmax = float(words[cont])
	elif word == 'iy':				# iy
		cont += 1
		ymin = float(words[cont])
		cont += 1
		ymax = float(words[cont])
	cont += 1

while cont < nw:
	word = words[cont]
	if word == 'n': 				# n : namep
		cont += 1
		namep = words[cont]
		print '***', namep
	elif word == 'c':				# c : colore
		cont += 1
		colore = words[cont]
	elif word == 'y':				# y : ydata
		inY = True
		ydata = []
		while inY and cont < nw:
			cont += 1
			s = words[cont]
			if s in klist:
				inY = False
				cont -= 1
			else:
				if isfloat(s):
					ydata.append(float(s))
				else:
					print 'errore,', s
					sys.exit(2)
	elif word == 'x':				# x : xd
		inX = True
		cx = 0
		while inX and cont < nw:
			cont += 1
			s = words[cont]
			if s in klist:
				inX = False
				cont -= 1
			else:
				if isfloat(s):
					if cx < len(dx):
						dx[cx] = float(s)
					else:
						dx.append(float(s))
				else:
					print 'errore,', s
					sys.exit(2)
			cx += 1
	elif word == '|.':				# |. fine serie
		xdata = dassex(dx, len(ydata))
		plt.plot(xdata, ydata, colore, linewidth = 2.0)

	cont += 1

if titolo != '':
	plt.title(titolo)
if xmin != '' and xmax != '':
	plt.xlim(xmin, xmax)
if ymin != '' and ymax != '':
	plt.ylim(ymin, ymax)
if gridp:
	plt.grid(True)
plt.show()

Come si vede dopo l’importazione di due moduli –numpy e matplotlib– ci sono in totale mezza dozzina di chiamate a funzioni specifiche del package (quelle che iniziano con plt.). Tutto il resto è fuffa, o meglio lettura e preparazione dei dati. Lì mi sono divertito (più di due settimane senza potersi collegare a teh toobz! avete idea?), mancherebbero ancora alcune parti di gestione degli errori, etichette degli assi, legenda, note nel grafico ma vengono lasciate come esercizio (sembro un dr.prof. vero?).

Questo è il file dei dati relativo alla figura precedente

primo test
t ecco_la_mia_prova
ix 100 125
iy -1 16

serie pi
.|
n pi
c r
y 3 1  4 1 5 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4 8
x 100
|.

serie e
.|
n e
c b
y 2 7 1 8 2 8 1 8 2 8 4 5 9 0 4 5 2 3 5 3 6
x 105
|.

serie decFib
.|
n decFib
c g
y .1 .1 .2 .3 .5 .8 1.3 2.1 3.4 5.5 8.9 14.4
x 100 2
|.

credo siano utili alcune spiegazioni, eccole.

  • La definizione di una curva inizia con .| e termina con |. e può contenere n = nome della curva (una sola parola), c = colore della curva, y = serie di dati, obbligatorio, x = ascissa iniziale, eventualmente seguita dal passo.
  • Tutto quello che è esterno ai marker di inizio e fine curva vengono ignorati ad eccezione di t = titolo del grafico, ix = valori iniziale e finale delle ascisse e iy = idem per le ordinate. per il titolo il limite a una sola parola sarebbe eccessivo e allora ho taroccato il tutto in modo che _ venga sostituito da uno spazio.

Con molta fantasia se lo script viene lanciato senza parametri questo legge il file con il nome “dati” (originale vero?). Se si vuole processare un file diverso basta passargli il nome come primo parametro, come in figura

le serie sono le stesse ma chissà com’è cambiato il file dei dati? Pochissimo, c’è l’istruzione di disegnare la griglia !g, cambiano i colori (OK, la definizione di colore è un tantino estesa e comprende anche il tipo di linea), comunque ecco il file datig

primo test
t ecco_la_mia_prova
ix 100 125
iy -1 15
!g

serie pi
marker
.|
n pi
c mD
y 3 1  4 1 5 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4 8
x 100
|.

linea tratteggiata
.|
n pi
c r--
y 3 1  4 1 5 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4 8
x 100
|.

serie e
.|
n e
c b:
y 2 7 1 8 2 8 1 8 2 8 4 5 9 0 4 5 2 3 5 3 6
x 105
|.

serie decFib
.|
n decFib
c g
y .1 .1 .2 .3 .5 .8 1.3 2.1 3.4 5.5 8.9 14.4
x 100 2
|.

Una cosa ancora: la toolbar dei grafici ha parecchie cose interessanti, lasciate al solito come esercizio per il lettore. Il pulsante più a destra, quello con il dischetto, consente di salvare il grafico in vari formati, Oltre agli usuali png e pdf sono intrigosi svg, ps e eps.
Ecco l’inizio di datig.svg

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.sourceforge.net/) -->
<svg width="585pt" height="441pt" viewBox="0 0 585 441"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   version="1.1"
   id="svg1">
<g id="figure1">
<g id="patch1">
<path style="fill: #ffffff; stroke: #ffffff; stroke-width: 1.000000; stroke-linejoin: round; stroke-linecap: square;  opacity: 1.000000"  d="M0.000000 441.000000L585.000000 441.000000L585.000000 0.000000
L0.000000 0.000000L0.000000 441.000000"/>
</g>
<g id="axes1">
<g id="patch2">
<path style="fill: #ffffff; opacity: 1.000000"  d="M73.125000 396.900000L526.500000 396.900000L526.500000 44.100000
L73.125000 44.100000L73.125000 396.900000"/>
</g>

il file completo è formato da 387 linee per complessivi 36924 caratteri; e questo l’inizio di datig.eps

%!PS-Adobe-3.0 EPSF-3.0
%%Title: /media/algol/lab/OKp/matplotlib/diag/diagg.eps
%%Creator: matplotlib version 0.99.3, http://matplotlib.sourceforge.net/
%%CreationDate: Thu May 19 09:09:55 2011
%%Orientation: portrait
%%BoundingBox: 13 175 598 616
%%EndComments
%%BeginProlog
/mpldict 8 dict def
mpldict begin
/m { moveto } bind def
/l { lineto } bind def
/r { rlineto } bind def
/c { curveto } bind def
/cl { closepath } bind def
/box {
m
1 index 0 r
0 exch r
neg 0 r
cl
} bind def

composto da 1155 righe per 18101 caratteri.

Un’altra cosa: con matplotlib si possono fare tantissimissime cose, per esempio, limitandoci alle scritte e copiando un esempio trovato nel manuale ecco cosa ho ottenuto
con questo script

# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt
fig = plt.figure()
fig.suptitle('bold figure suptitle', fontsize=14, fontweight='bold')
ax = fig.add_subplot(111)

ax.set_title('teh title')
ax.set_xlabel('xlabel')
ax.set_ylabel('ylabel')

ax.text(3, 9, 'boxed italics text in data coords', style='italic',
					bbox={'facecolor':'red', 'alpha':0.5, 'pad':10})

ax.text(0.5, 8, r'an equation: $E=mc^2$', fontsize=15)
ax.text(0.5, 7, r'$s(t) = \mathcal{A}\ \mathrm{sin}(2 \omega t)$')

ax.text(4, 8, unicode('unicode: Institut f\374r Festk\366rperphysik', 'latin-1'))
ax.text(4, 7.5, u'pös scriwlo anche parei?', color='red')
ax.text(4, 7, u"va bin se 'd vöhle duvrè 'l piemuntèis", color='brown')

ax.text(1, 1, 'questo ci resta di trasverso?', size = 24, rotation = 30,
					color = 'red', family = 'monospace', style = 'oblique',
					fontweight='heavy')
ax.text(1, .3, 'questo ci resta di trasverso?', size = 24, rotation = 30,
					color = 'red', family = 'monospace', style = 'italic',
					fontweight='ultralight')

ax.text(0.95, 0.01, 'colored text in axes coords',
					verticalalignment='bottom', horizontalalignment='right',
					transform=ax.transAxes,
					color='green', fontsize=15)
ax.text(1, 6, ur'$\aleph_0$ questo per Zar $2^{\aleph_0}$', size = 24)
ax.text(1, 5, r'$\mathfrak{e\ anche\ questo\ \ C} \ \ \ \bigotimes$', size = 24)
ax.text(6, 1, r'$t=\sum_{i=0}^\infty x_i^2$', size = 36)

ax.plot([4], [1], 'o')
ax.annotate('annotate', xy=(4, 1), xytext=(1, 4),
arrowprops=dict(facecolor='black', shrink=0.05))
ax.axis([0, 10, 0, 10])

plt.show()

Mmmh, mi sa che prima o poi bisognerà darlare di TeX o LaTeX 8)

Un’ultima cosa: il manuale di Scilab ricordava l’approssimazione con cui si riesce a rappresentare i numeri reali. Non è una cosa che capita solo con Scilab (e con il Fortran), ecco cosa ho scoperto (scoperto è una parola grossa, lo so!) nella costruzione dello script diag.py

Ah! se i nostri antenati avessero scelto di contare base 8, come usavano i Padri Fondatori di Unix! Adesso lo ammetto è troppo tardi, ci dobbiamo rassegnare.

About these ads
Post a comment or leave a trackback: Trackback URL.

Commenti

  • zar  On 28 maggio 2011 at 10:48

    :-)

  • Mirko  On 17 gennaio 2012 at 18:42

    Ciao volevo farti una domanda, ho realizzato un app che stampa a video un elettrocardiogramma, gli indici che vengono stampati in ordinate e ascisse sono quelle relativi agli array da cui prendo i dati, ora:
    c’è un modo per settare queste etichette di valori con una scala che decido io?
    spero di essermi spiegato bene
    a presto

Trackbacks

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

Iscriviti

Ricevi al tuo indirizzo email tutti i nuovi post del sito.

Unisciti agli altri 71 follower

%d blogger cliccano Mi Piace per questo: