Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/plot/matrix_plot.py
8815 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
A plot title can be added to the matrix plot.::
422
423
sage: matrix_plot(identity_matrix(50), origin='lower', title='not identity')
424
425
The title position is adjusted upwards if the ``origin`` keyword is set
426
to ``"upper"`` (this is the default).::
427
428
sage: matrix_plot(identity_matrix(50), title='identity')
429
430
TESTS::
431
432
sage: P.<t> = RR[]
433
sage: matrix_plot(random_matrix(P, 3, 3))
434
Traceback (most recent call last):
435
...
436
TypeError: cannot coerce nonconstant polynomial to float
437
438
::
439
440
sage: matrix_plot([1,2,3])
441
Traceback (most recent call last):
442
...
443
TypeError: mat must be a Matrix or a two dimensional array
444
445
::
446
447
sage: matrix_plot([[sin(x), cos(x)], [1, 0]])
448
Traceback (most recent call last):
449
...
450
TypeError: mat must be a Matrix or a two dimensional array
451
452
Test that sparse matrices also work with subdivisions::
453
454
sage: matrix_plot(sparse, subdivisions=True, subdivision_boundaries=[[2,4],[6,8]])
455
"""
456
import numpy as np
457
import scipy.sparse as scipysparse
458
from sage.plot.all import Graphics
459
from sage.matrix.matrix import is_Matrix
460
from sage.rings.all import RDF
461
orig_mat=mat
462
if is_Matrix(mat):
463
sparse = mat.is_sparse()
464
if sparse:
465
entries = list(mat._dict().items())
466
try:
467
data = np.asarray([d for _,d in entries], dtype=float)
468
except Exception:
469
raise ValueError, "can not convert entries to floating point numbers"
470
positions = np.asarray([[row for (row,col),_ in entries],
471
[col for (row,col),_ in entries]], dtype=int)
472
mat = scipysparse.coo_matrix((data,positions), shape=(mat.nrows(), mat.ncols()))
473
else:
474
mat = mat.change_ring(RDF).numpy()
475
elif hasattr(mat, 'tocoo'):
476
sparse = True
477
else:
478
sparse = False
479
480
481
try:
482
if sparse:
483
xy_data_array = mat
484
else:
485
xy_data_array = np.asarray(mat, dtype = float)
486
except TypeError:
487
raise TypeError, "mat must be a Matrix or a two dimensional array"
488
except ValueError:
489
raise ValueError, "can not convert entries to floating point numbers"
490
491
if len(xy_data_array.shape) < 2:
492
raise TypeError, "mat must be a Matrix or a two dimensional array"
493
494
xrange = (0, xy_data_array.shape[1])
495
yrange = (0, xy_data_array.shape[0])
496
497
if options['subdivisions'] and options['subdivision_options']['boundaries'] is None:
498
options['subdivision_options']['boundaries']=orig_mat.get_subdivisions()
499
500
# Custom position the title. Otherwise it overlaps with tick labels
501
if options['origin'] == 'upper' and 'title_pos' not in options:
502
options['title_pos'] = (0.5, 1.05)
503
504
g = Graphics()
505
g._set_extra_kwds(Graphics._extract_kwds_for_show(options))
506
g.add_primitive(MatrixPlot(xy_data_array, xrange, yrange, options))
507
return g
508
509