Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/matrix_plot.py
4034 views
1
"""
2
Matrix Plots
3
"""
4
5
#*****************************************************************************
6
# Copyright (C) 2006 Alex Clemesha <[email protected]>,
7
# William Stein <[email protected]>,
8
# 2008 Mike Hansen <[email protected]>,
9
#
10
# Distributed under the terms of the GNU General Public License (GPL)
11
#
12
# This code is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
# General Public License for more details.
16
#
17
# The full text of the GPL is available at:
18
#
19
# http://www.gnu.org/licenses/
20
#*****************************************************************************
21
from sage.plot.primitive import GraphicPrimitive
22
from sage.misc.decorators import options, suboptions
23
from sage.plot.colors import get_cmap
24
25
class MatrixPlot(GraphicPrimitive):
26
"""
27
Primitive class for the matrix plot graphics type. See
28
``matrix_plot?`` for help actually doing matrix plots.
29
30
INPUT:
31
32
- ``xy_data_array`` - list of lists giving matrix values corresponding to
33
the grid
34
35
- ``xrange`` - tuple of 2 floats indicating range for horizontal direction
36
(number of columns in the matrix)
37
38
- ``yrange`` - tuple of 2 floats indicating range for vertical direction
39
(number of rows in the matrix)
40
41
- ``options`` - dict of valid plot options to pass to constructor
42
43
EXAMPLES:
44
45
Note this should normally be used indirectly via :func:`matrix_plot`::
46
47
sage: from sage.plot.matrix_plot import MatrixPlot
48
sage: M = MatrixPlot([[1,3],[2,4]],(1,2),(2,3),options={'cmap':'winter'})
49
sage: M
50
MatrixPlot defined by a 2 x 2 data grid
51
sage: M.yrange
52
(2, 3)
53
sage: M.xy_data_array
54
[[1, 3], [2, 4]]
55
sage: M.options()
56
{'cmap': 'winter'}
57
58
Extra options will get passed on to :meth:`~Graphics.show`, as long as they are valid::
59
60
sage: matrix_plot([[1, 0], [0, 1]], fontsize=10)
61
sage: matrix_plot([[1, 0], [0, 1]]).show(fontsize=10) # These are equivalent
62
63
TESTS:
64
65
We test creating a matrix plot::
66
67
sage: matrix_plot([[mod(i,5)^j for i in range(5)] for j in range(1,6)])
68
"""
69
def __init__(self, xy_data_array, xrange, yrange, options):
70
"""
71
Initializes base class MatrixPlot.
72
73
EXAMPLES::
74
75
sage: M = matrix_plot([[mod(i,5)^j for i in range(5)] for j in range(1,6)], cmap='jet')
76
sage: M[0].xrange
77
(0, 5)
78
sage: M[0].options()['cmap']
79
'jet'
80
sage: M[0].xy_array_row
81
5
82
"""
83
self.xrange = xrange
84
self.yrange = yrange
85
self.xy_data_array = xy_data_array
86
if hasattr(xy_data_array, 'shape'):
87
self.xy_array_row = xy_data_array.shape[0]
88
self.xy_array_col = xy_data_array.shape[1]
89
else:
90
self.xy_array_row = len(xy_data_array)
91
self.xy_array_col = len(xy_data_array[0])
92
GraphicPrimitive.__init__(self, options)
93
94
def get_minmax_data(self):
95
"""
96
Returns a dictionary with the bounding box data.
97
98
EXAMPLES::
99
100
sage: m = matrix_plot(matrix([[1,3,5,1],[2,4,5,6],[1,3,5,7]]))[0]
101
sage: list(sorted(m.get_minmax_data().items()))
102
[('xmax', 3.5), ('xmin', -0.5), ('ymax', -0.5), ('ymin', 2.5)]
103
104
105
"""
106
from sage.plot.plot import minmax_data
107
limits= minmax_data(self.xrange, self.yrange, dict=True)
108
if self.options()['origin']!='lower':
109
# flip y-axis so that the picture looks correct.
110
limits['ymin'],limits['ymax']=limits['ymax'],limits['ymin']
111
112
# center the matrix so that, for example, the square representing the
113
# (0,0) entry is centered on the origin.
114
for k,v in limits.iteritems():
115
limits[k]-=0.5
116
return limits
117
118
def _allowed_options(self):
119
"""
120
Return the allowed options for the MatrixPlot class.
121
122
EXAMPLES::
123
124
sage: M = matrix_plot([[sin(i*j) for i in range(5)] for j in range(5)])
125
sage: isinstance(M[0]._allowed_options(),dict)
126
True
127
"""
128
return {'cmap':"""the name of a predefined colormap,
129
a list of colors, or an instance of a
130
matplotlib Colormap. Type: import matplotlib.cm; matplotlib.cm.datad.keys()
131
for available colormap names.""",
132
'colorbar': "Include a colorbar indicating the levels (dense matrices only)",
133
'colorbar_options': "a dictionary of options for colorbars",
134
'zorder':"The layer level in which to draw",
135
'marker':"The marker for sparse plots",
136
'markersize':"The marker size for sparse plots",
137
'norm': "The normalization function",
138
'vmin': "The minimum value",
139
'vmax': "The maximum value",
140
'origin': "If 'lower', draw the matrix with the first row on the bottom of the graph",
141
'subdivisions': "If True, draw subdivisions of the matrix",
142
'subdivision_options': "Options (boundaries and style) of the subdivisions"}
143
144
def _repr_(self):
145
"""
146
String representation of MatrixPlot primitive.
147
148
EXAMPLES::
149
150
sage: M = matrix_plot([[sin(i*j) for i in range(5)] for j in range(5)])
151
sage: m = M[0]; m
152
MatrixPlot defined by a 5 x 5 data grid
153
"""
154
return "MatrixPlot defined by a %s x %s data grid"%(self.xy_array_row, self.xy_array_col)
155
156
def _render_on_subplot(self, subplot):
157
"""
158
TESTS::
159
160
sage: matrix_plot(random_matrix(RDF, 50), cmap='jet')
161
"""
162
options = self.options()
163
cmap = get_cmap(options.pop('cmap',None))
164
origin=options['origin']
165
166
norm=options['norm']
167
168
if norm=='value':
169
import matplotlib
170
norm=matplotlib.colors.NoNorm()
171
172
if options['subdivisions']:
173
subdiv_options=options['subdivision_options']
174
if isinstance(subdiv_options['boundaries'], (list, tuple)):
175
rowsub,colsub=subdiv_options['boundaries']
176
else:
177
rowsub=subdiv_options['boundaries']
178
colsub=subdiv_options['boundaries']
179
if isinstance(subdiv_options['style'], (list, tuple)):
180
rowstyle,colstyle=subdiv_options['style']
181
else:
182
rowstyle=subdiv_options['style']
183
colstyle=subdiv_options['style']
184
if rowstyle is None:
185
rowstyle=dict()
186
if colstyle is None:
187
colstyle=dict()
188
189
# Make line objects for subdivisions
190
from line import line2d
191
lim=self.get_minmax_data()
192
# First draw horizontal lines representing row subdivisions
193
for y in rowsub:
194
l=line2d([(lim['xmin'],y-0.5), (lim['xmax'],y-0.5)], **rowstyle)[0]
195
l._render_on_subplot(subplot)
196
for x in colsub:
197
l=line2d([(x-0.5, lim['ymin']), (x-0.5, lim['ymax'])], **colstyle)[0]
198
l._render_on_subplot(subplot)
199
200
if hasattr(self.xy_data_array, 'tocoo'):
201
# Sparse matrix -- use spy
202
opts=options.copy()
203
for opt in ['vmin', 'vmax', 'norm', 'origin','subdivisions','subdivision_options',
204
'colorbar','colorbar_options']:
205
del opts[opt]
206
if origin=='lower':
207
subplot.spy(self.xy_data_array.tocsr()[::-1], **opts)
208
else:
209
subplot.spy(self.xy_data_array, **opts)
210
else:
211
opts = dict(cmap=cmap, interpolation='nearest', aspect='equal',
212
norm=norm, vmin=options['vmin'], vmax=options['vmax'],
213
origin=origin,zorder=options.get('zorder',None))
214
image=subplot.imshow(self.xy_data_array, **opts)
215
216
if options.get('colorbar', False):
217
colorbar_options = options['colorbar_options']
218
from matplotlib import colorbar
219
cax,kwds=colorbar.make_axes_gridspec(subplot,**colorbar_options)
220
cb=colorbar.Colorbar(cax,image, **kwds)
221
222
if origin=='upper':
223
subplot.xaxis.tick_top()
224
elif origin=='lower':
225
subplot.xaxis.tick_bottom()
226
subplot.xaxis.set_ticks_position('both') #only tick marks, not tick labels
227
228
229
230
231
@suboptions('colorbar', orientation='vertical', format=None)
232
@suboptions('subdivision',boundaries=None, style=None)
233
@options(cmap='gray',marker='.',frame=True, axes=False, norm=None,
234
vmin=None, vmax=None, origin='upper',ticks_integer=True,
235
subdivisions=False, colorbar=False)
236
def matrix_plot(mat, **options):
237
r"""
238
A plot of a given matrix or 2D array.
239
240
If the matrix is dense, each matrix element is given a different
241
color value depending on its relative size compared to the other
242
elements in the matrix. If the matrix is sparse, colors only
243
indicate whether an element is nonzero or zero, so the plot
244
represents the sparsity pattern of the matrix.
245
246
The tick marks drawn on the frame axes denote the row numbers
247
(vertical ticks) and the column numbers (horizontal ticks) of the
248
matrix.
249
250
INPUT:
251
252
- ``mat`` - a 2D matrix or array
253
254
The following input must all be passed in as named parameters, if
255
default not used:
256
257
- ``cmap`` - a colormap (default: 'gray'), the name of
258
a predefined colormap, a list of colors,
259
or an instance of a matplotlib Colormap.
260
Type: ``import matplotlib.cm; matplotlib.cm.datad.keys()``
261
for available colormap names.
262
263
- ``colorbar`` -- boolean (default: False) Show a colorbar or not (dense matrices only).
264
265
The following options are used to adjust the style and placement
266
of colorbars. They have no effect if a colorbar is not shown.
267
268
- ``colorbar_orientation`` -- string (default: 'vertical'),
269
controls placement of the colorbar, can be either 'vertical'
270
or 'horizontal'
271
272
- ``colorbar_format`` -- a format string, this is used to format
273
the colorbar labels.
274
275
- ``colorbar_options`` -- a dictionary of options for the matplotlib
276
colorbar API. Documentation for the :mod:`matplotlib.colorbar` module
277
has details.
278
279
- ``norm`` - If None (default), the value range is scaled to the interval
280
[0,1]. If 'value', then the actual value is used with no
281
scaling. A :class:`matplotlib.colors.Normalize` instance may
282
also passed.
283
284
- ``vmin`` - The minimum value (values below this are set to this value)
285
286
- ``vmax`` - The maximum value (values above this are set to this value)
287
288
- ``origin`` - If 'upper' (default), the first row of the matrix
289
is on the top of the graph. If 'lower', the first row is on the
290
bottom of the graph.
291
292
- ``subdivisions`` - If True, plot the subdivisions of the matrix as lines.
293
294
- ``subdivision_boundaries`` - a list of lists in the form
295
``[row_subdivisions, column_subdivisions]``, which specifies
296
the row and column subdivisions to use. If not specified,
297
defaults to the matrix subdivisions
298
299
- ``subdivision_style`` - a dictionary of properties passed
300
on to the :func:`~sage.plot.line.line2d` command for plotting
301
subdivisions. If this is a two-element list or tuple, then it
302
specifies the styles of row and column divisions, respectively.
303
304
EXAMPLES:
305
306
A matrix over `\ZZ` colored with different grey levels::
307
308
sage: matrix_plot(matrix([[1,3,5,1],[2,4,5,6],[1,3,5,7]]))
309
310
Here we make a random matrix over `\RR` and use ``cmap='hsv'``
311
to color the matrix elements different RGB colors::
312
313
sage: matrix_plot(random_matrix(RDF, 50), cmap='hsv')
314
315
By default, entries are scaled to the interval [0,1] before
316
determining colors from the color map. That means the two plots
317
below are the same::
318
319
sage: P = matrix_plot(matrix(2,[1,1,3,3]))
320
sage: Q = matrix_plot(matrix(2,[2,2,3,3]))
321
sage: P; Q
322
323
However, we can specify which values scale to 0 or 1 with the
324
``vmin`` and ``vmax`` parameters (values outside the range are
325
clipped). The two plots below are now distinguished::
326
327
sage: P = matrix_plot(matrix(2,[1,1,3,3]), vmin=0, vmax=3, colorbar=True)
328
sage: Q = matrix_plot(matrix(2,[2,2,3,3]), vmin=0, vmax=3, colorbar=True)
329
sage: P; Q
330
331
We can also specify a norm function of 'value', which means that
332
there is no scaling performed::
333
334
sage: matrix_plot(random_matrix(ZZ,10)*.05, norm='value', colorbar=True)
335
336
Matrix subdivisions can be plotted as well::
337
338
sage: m=random_matrix(RR,10)
339
sage: m.subdivide([2,4],[6,8])
340
sage: matrix_plot(m, subdivisions=True, subdivision_style=dict(color='red',thickness=3))
341
342
You can also specify your own subdivisions and separate styles
343
for row or column subdivisions::
344
345
sage: m=random_matrix(RR,10)
346
sage: matrix_plot(m, subdivisions=True, subdivision_boundaries=[[2,4],[6,8]], subdivision_style=[dict(color='red',thickness=3),dict(linestyle='--',thickness=6)])
347
348
Generally matrices are plotted with the (0,0) entry in the upper
349
left. However, sometimes if we are plotting an image, we'd like
350
the (0,0) entry to be in the lower left. We can do that with the
351
``origin`` argument::
352
353
sage: matrix_plot(identity_matrix(100), origin='lower')
354
355
Another random plot, but over `\GF{389}`::
356
357
sage: m = random_matrix(GF(389), 10)
358
sage: matrix_plot(m, cmap='Oranges')
359
360
It also works if you lift it to the polynomial ring::
361
362
sage: matrix_plot(m.change_ring(GF(389)['x']), cmap='Oranges')
363
364
We have several options for colorbars::
365
366
sage: matrix_plot(random_matrix(RDF, 50), colorbar=True, colorbar_orientation='horizontal')
367
368
::
369
370
sage: matrix_plot(random_matrix(RDF, 50), colorbar=True, colorbar_format='%.3f')
371
372
The length of a color bar and the length of the adjacent
373
matrix plot dimension may be quite different. This example
374
shows how to adjust the length of the colorbar by passing a
375
dictionary of options to the matplotlib colorbar routines. ::
376
377
sage: m = random_matrix(ZZ, 40, 80, x=-10, y=10)
378
sage: m.plot(colorbar=True, colorbar_orientation='vertical',
379
... colorbar_options={'shrink':0.50})
380
381
Here we plot a random sparse matrix::
382
383
sage: sparse = matrix(dict([((randint(0, 10), randint(0, 10)), 1) for i in xrange(100)]))
384
sage: matrix_plot(sparse)
385
386
::
387
388
sage: A=random_matrix(ZZ,100000,density=.00001,sparse=True)
389
sage: matrix_plot(A,marker=',')
390
391
As with dense matrices, sparse matrix entries are automatically
392
converted to floating point numbers before plotting. Thus the
393
following works::
394
395
sage: b=random_matrix(GF(2),200,sparse=True,density=0.01)
396
sage: matrix_plot(b)
397
398
While this returns an error::
399
400
sage: b=random_matrix(CDF,200,sparse=True,density=0.01)
401
sage: matrix_plot(b)
402
Traceback (most recent call last):
403
...
404
ValueError: can not convert entries to floating point numbers
405
406
To plot the absolute value of a complex matrix, use the
407
``apply_map`` method::
408
409
sage: b=random_matrix(CDF,200,sparse=True,density=0.01)
410
sage: matrix_plot(b.apply_map(abs))
411
412
Plotting lists of lists also works::
413
414
sage: matrix_plot([[1,3,5,1],[2,4,5,6],[1,3,5,7]])
415
416
As does plotting of NumPy arrays::
417
418
sage: import numpy
419
sage: matrix_plot(numpy.random.rand(10, 10))
420
421
TESTS::
422
423
sage: P.<t> = RR[]
424
sage: matrix_plot(random_matrix(P, 3, 3))
425
Traceback (most recent call last):
426
...
427
TypeError: cannot coerce nonconstant polynomial to float
428
429
::
430
431
sage: matrix_plot([1,2,3])
432
Traceback (most recent call last):
433
...
434
TypeError: mat must be a Matrix or a two dimensional array
435
436
::
437
438
sage: matrix_plot([[sin(x), cos(x)], [1, 0]])
439
Traceback (most recent call last):
440
...
441
ValueError: can not convert entries to floating point numbers
442
443
Test that sparse matrices also work with subdivisions::
444
445
sage: matrix_plot(sparse, subdivisions=True, subdivision_boundaries=[[2,4],[6,8]])
446
"""
447
import numpy as np
448
import scipy.sparse as scipysparse
449
from sage.plot.all import Graphics
450
from sage.matrix.all import is_Matrix
451
from sage.rings.all import RDF
452
orig_mat=mat
453
if is_Matrix(mat):
454
sparse = mat.is_sparse()
455
if sparse:
456
entries = list(mat._dict().items())
457
try:
458
data = np.asarray([d for _,d in entries], dtype=float)
459
except:
460
raise ValueError, "can not convert entries to floating point numbers"
461
positions = np.asarray([[row for (row,col),_ in entries],
462
[col for (row,col),_ in entries]], dtype=int)
463
mat = scipysparse.coo_matrix((data,positions), shape=(mat.nrows(), mat.ncols()))
464
else:
465
mat = mat.change_ring(RDF).numpy()
466
elif hasattr(mat, 'tocoo'):
467
sparse = True
468
else:
469
sparse = False
470
471
472
try:
473
if sparse:
474
xy_data_array = mat
475
else:
476
xy_data_array = np.asarray(mat, dtype = float)
477
except TypeError:
478
raise TypeError, "mat must be a Matrix or a two dimensional array"
479
except ValueError:
480
raise ValueError, "can not convert entries to floating point numbers"
481
482
if len(xy_data_array.shape) < 2:
483
raise TypeError, "mat must be a Matrix or a two dimensional array"
484
485
xrange = (0, xy_data_array.shape[1])
486
yrange = (0, xy_data_array.shape[0])
487
488
if options['subdivisions'] and options['subdivision_options']['boundaries'] is None:
489
options['subdivision_options']['boundaries']=orig_mat.get_subdivisions()
490
491
g = Graphics()
492
g._set_extra_kwds(Graphics._extract_kwds_for_show(options))
493
g.add_primitive(MatrixPlot(xy_data_array, xrange, yrange, options))
494
return g
495
496