Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
r""" Tables
Display a rectangular array as a table, either in plain text, LaTeX, or html. See the documentation for :class:`table` for details and examples.
AUTHORS:
- John H. Palmieri (2012-11) """ from __future__ import absolute_import from six.moves import cStringIO as StringIO from six.moves import range, zip
from sage.structure.sage_object import SageObject from sage.misc.cachefunc import cached_method
class table(SageObject): r""" Display a rectangular array as a table, either in plain text, LaTeX, or html.
INPUT:
- ``rows`` (default ``None``) - a list of lists (or list of tuples, etc.), containing the data to be displayed. - ``columns`` (default ``None``) - a list of lists (etc.), containing the data to be displayed, but stored as columns. Set either ``rows`` or ``columns``, but not both. - ``header_row`` (default ``False``) - if ``True``, first row is highlighted. - ``header_column`` (default ``False``) - if ``True``, first column is highlighted. - ``frame`` (default ``False``) - if ``True``, put a box around each cell. - ``align`` (default 'left') - the alignment of each entry: either 'left', 'center', or 'right'
EXAMPLES::
sage: rows = [['a', 'b', 'c'], [100,2,3], [4,5,60]] sage: table(rows) a b c 100 2 3 4 5 60 sage: latex(table(rows)) \begin{tabular}{lll} a & b & c \\ $100$ & $2$ & $3$ \\ $4$ & $5$ & $60$ \\ \end{tabular}
If ``header_row`` is ``True``, then the first row is highlighted. If ``header_column`` is ``True``, then the first column is highlighted. If ``frame`` is ``True``, then print a box around every "cell". ::
sage: table(rows, header_row=True) a b c +-----+---+----+ 100 2 3 4 5 60 sage: latex(table(rows, header_row=True)) \begin{tabular}{lll} a & b & c \\ \hline $100$ & $2$ & $3$ \\ $4$ & $5$ & $60$ \\ \end{tabular} sage: table(rows=rows, frame=True) +-----+---+----+ | a | b | c | +-----+---+----+ | 100 | 2 | 3 | +-----+---+----+ | 4 | 5 | 60 | +-----+---+----+ sage: latex(table(rows=rows, frame=True)) \begin{tabular}{|l|l|l|} \hline a & b & c \\ \hline $100$ & $2$ & $3$ \\ \hline $4$ & $5$ & $60$ \\ \hline \end{tabular} sage: table(rows, header_column=True, frame=True) +-----++---+----+ | a || b | c | +-----++---+----+ | 100 || 2 | 3 | +-----++---+----+ | 4 || 5 | 60 | +-----++---+----+ sage: latex(table(rows, header_row=True, frame=True)) \begin{tabular}{|l|l|l|} \hline a & b & c \\ \hline \hline $100$ & $2$ & $3$ \\ \hline $4$ & $5$ & $60$ \\ \hline \end{tabular} sage: table(rows, header_column=True) a | b c 100 | 2 3 4 | 5 60
The argument ``header_row`` can, instead of being ``True`` or ``False``, be the contents of the header row, so that ``rows`` consists of the data, while ``header_row`` is the header information. The same goes for ``header_column``. Passing lists for both arguments simultaneously is not supported. ::
sage: table([(x,n(sin(x), digits=2)) for x in [0..3]], header_row=["$x$", r"$\sin(x)$"], frame=True) +-----+-----------+ | $x$ | $\sin(x)$ | +=====+===========+ | 0 | 0.00 | +-----+-----------+ | 1 | 0.84 | +-----+-----------+ | 2 | 0.91 | +-----+-----------+ | 3 | 0.14 | +-----+-----------+
You can create the transpose of this table in several ways, for example, "by hand," that is, changing the data defining the table::
sage: table(rows=[[x for x in [0..3]], [n(sin(x), digits=2) for x in [0..3]]], header_column=['$x$', r'$\sin(x)$'], frame=True) +-----------++------+------+------+------+ | $x$ || 0 | 1 | 2 | 3 | +-----------++------+------+------+------+ | $\sin(x)$ || 0.00 | 0.84 | 0.91 | 0.14 | +-----------++------+------+------+------+
or by passing the original data as the ``columns`` of the table and using ``header_column`` instead of ``header_row``::
sage: table(columns=[(x,n(sin(x), digits=2)) for x in [0..3]], header_column=['$x$', r'$\sin(x)$'], frame=True) +-----------++------+------+------+------+ | $x$ || 0 | 1 | 2 | 3 | +-----------++------+------+------+------+ | $\sin(x)$ || 0.00 | 0.84 | 0.91 | 0.14 | +-----------++------+------+------+------+
or by taking the :meth:`transpose` of the original table::
sage: table(rows=[(x,n(sin(x), digits=2)) for x in [0..3]], header_row=['$x$', r'$\sin(x)$'], frame=True).transpose() +-----------++------+------+------+------+ | $x$ || 0 | 1 | 2 | 3 | +-----------++------+------+------+------+ | $\sin(x)$ || 0.00 | 0.84 | 0.91 | 0.14 | +-----------++------+------+------+------+
In either plain text or LaTeX, entries in tables can be aligned to the left (default), center, or right::
sage: table(rows, align='left') a b c 100 2 3 4 5 60 sage: table(rows, align='center') a b c 100 2 3 4 5 60 sage: table(rows, align='right', frame=True) +-----+---+----+ | a | b | c | +-----+---+----+ | 100 | 2 | 3 | +-----+---+----+ | 4 | 5 | 60 | +-----+---+----+
To generate HTML you should use ``html(table(...))``::
sage: data = [["$x$", r"$\sin(x)$"]] + [(x,n(sin(x), digits=2)) for x in [0..3]] sage: output = html(table(data, header_row=True, frame=True)) sage: type(output) <class 'sage.misc.html.HtmlFragment'> sage: print(output) <div class="notruncate"> <table border="1" class="table_form"> <tbody> <tr> <th><script type="math/tex">x</script></th> <th><script type="math/tex">\sin(x)</script></th> </tr> <tr class ="row-a"> <td><script type="math/tex">0</script></td> <td><script type="math/tex">0.00</script></td> </tr> <tr class ="row-b"> <td><script type="math/tex">1</script></td> <td><script type="math/tex">0.84</script></td> </tr> <tr class ="row-a"> <td><script type="math/tex">2</script></td> <td><script type="math/tex">0.91</script></td> </tr> <tr class ="row-b"> <td><script type="math/tex">3</script></td> <td><script type="math/tex">0.14</script></td> </tr> </tbody> </table> </div>
It is an error to specify both ``rows`` and ``columns``::
sage: table(rows=[[1,2,3], [4,5,6]], columns=[[0,0,0], [0,0,1024]]) Traceback (most recent call last): ... ValueError: Don't set both 'rows' and 'columns' when defining a table.
sage: table(columns=[[0,0,0], [0,0,1024]]) 0 0 0 0 0 1024
Note that if ``rows`` is just a list or tuple, not nested, then it is treated as a single row::
sage: table([1,2,3]) 1 2 3
Also, if you pass a non-rectangular array, the longer rows or columns get truncated::
sage: table([[1,2,3,7,12], [4,5]]) 1 2 4 5 sage: table(columns=[[1,2,3], [4,5,6,7]]) 1 4 2 5 3 6
TESTS::
sage: TestSuite(table([["$x$", r"$\sin(x)$"]] + ....: [(x,n(sin(x), digits=2)) for x in [0..3]], ....: header_row=True, frame=True)).run()
.. automethod:: _rich_repr_ """ def __init__(self, rows=None, columns=None, header_row=False, header_column=False, frame=False, align='left'): r""" EXAMPLES::
sage: table([1,2,3], frame=True) +---+---+---+ | 1 | 2 | 3 | +---+---+---+ """ # If both rows and columns are set, raise an error. # If columns is set, use its transpose for rows. # Set the rest of the options. else: else:
# Store rows as a tuple.
def __eq__(self, other): r""" Two tables are equal if and only if their data rowss and their options are the same.
EXAMPLES::
sage: rows = [['a', 'b', 'c'], [1,plot(sin(x)),3], [4,5,identity_matrix(2)]] sage: T = table(rows, header_row=True) sage: T2 = table(rows, header_row=True) sage: T is T2 False sage: T == T2 True sage: T2.options(frame=True) sage: T == T2 False """
def options(self, **kwds): r""" With no arguments, return the dictionary of options for this table. With arguments, modify options.
INPUT:
- ``header_row`` - if True, first row is highlighted. - ``header_column`` - if True, first column is highlighted. - ``frame`` - if True, put a box around each cell. - ``align`` - the alignment of each entry: either 'left', 'center', or 'right'
EXAMPLES::
sage: T = table([['a', 'b', 'c'], [1,2,3]]) sage: T.options()['align'], T.options()['frame'] ('left', False) sage: T.options(align='right', frame=True) sage: T.options()['align'], T.options()['frame'] ('right', True)
Note that when first initializing a table, ``header_row`` or ``header_column`` can be a list. In this case, during the initialization process, the header is merged with the rest of the data, so changing the header option later using ``table.options(...)`` doesn't affect the contents of the table, just whether the row or column is highlighed. When using this :meth:`options` method, no merging of data occurs, so here ``header_row`` and ``header_column`` should just be ``True`` or ``False``, not a list. ::
sage: T = table([[1,2,3], [4,5,6]], header_row=['a', 'b', 'c'], frame=True) sage: T +---+---+---+ | a | b | c | +===+===+===+ | 1 | 2 | 3 | +---+---+---+ | 4 | 5 | 6 | +---+---+---+ sage: T.options(header_row=False) sage: T +---+---+---+ | a | b | c | +---+---+---+ | 1 | 2 | 3 | +---+---+---+ | 4 | 5 | 6 | +---+---+---+
If you do specify a list for ``header_row``, an error is raised::
sage: T.options(header_row=['x', 'y', 'z']) Traceback (most recent call last): ... TypeError: header_row should be either True or False. """ else: else:
def transpose(self): r""" Return a table which is the transpose of this one: rows and columns have been interchanged. Several of the properties of the original table are preserved: whether a frame is present and any alignment setting. On the other hand, header rows are converted to header columns, and vice versa.
EXAMPLES::
sage: T = table([[1,2,3], [4,5,6]]) sage: T.transpose() 1 4 2 5 3 6 sage: T = table([[1,2,3], [4,5,6]], header_row=['x', 'y', 'z'], frame=True) sage: T.transpose() +---++---+---+ | x || 1 | 4 | +---++---+---+ | y || 2 | 5 | +---++---+---+ | z || 3 | 6 | +---++---+---+ """ header_row=self._options['header_column'], header_column=self._options['header_row'], frame=self._options['frame'], align=self._options['align'])
@cached_method def _widths(self): r""" The maximum widths for (the string representation of) each column. Used by the :meth:`_repr_` method.
EXAMPLES::
sage: table([['a', 'bb', 'ccccc'], [10, -12, 0], [1, 2, 3]])._widths() (2, 3, 5) """
def _repr_(self): r""" String representation of a table.
The class docstring has many examples; here is one more.
EXAMPLES::
sage: table([['a', 'bb', 'ccccc'], [10, -12, 0], [1, 2, 3]], align='right') # indirect doctest a bb ccccc 10 -12 0 1 2 3 """ return ""
else:
def _rich_repr_(self, display_manager, **kwds): """ Rich Output Magic Method
See :mod:`sage.repl.rich_output` for details.
EXAMPLES::
sage: from sage.repl.rich_output import get_display_manager sage: dm = get_display_manager() sage: t = table([1, 2, 3]) sage: t._rich_repr_(dm) # the doctest backend does not support html """
def _str_table_row(self, row, header_row=False): r""" String representation of a row of a table. Used by the :meth:`_repr_` method.
EXAMPLES::
sage: T = table([['a', 'bb', 'ccccc'], [10, -12, 0], [1, 2, 3]], align='right') sage: T._str_table_row([1,2,3]) ' 1 2 3\n' sage: T._str_table_row([1,2,3], True) ' 1 2 3\n+----+-----+-------+\n' sage: T.options(header_column=True) sage: T._str_table_row([1,2,3], True) ' 1 | 2 3\n+----+-----+-------+\n' sage: T.options(frame=True) sage: T._str_table_row([1,2,3], False) '| 1 || 2 | 3 |\n+----++-----+-------+\n'
Check that :trac:`14601` has been fixed::
sage: table([['111111', '222222', '333333']])._str_table_row([False,True,None], False) ' False True None\n' """
else:
else:
else:
else:
def _latex_(self): r""" LaTeX representation of a table.
If an entry is a Sage object, it is replaced by its LaTeX representation, delimited by dollar signs (i.e., ``x`` is replaced by ``$latex(x)$``). If an entry is a string, the dollar signs are not automatically added, so tables can include both plain text and mathematics.
OUTPUT:
String.
EXAMPLES::
sage: from sage.misc.table import table sage: a = [[r'$\sin(x)$', '$x$', 'text'], [1,34342,3], [identity_matrix(2),5,6]] sage: latex(table(a)) # indirect doctest \begin{tabular}{lll} $\sin(x)$ & $x$ & text \\ $1$ & $34342$ & $3$ \\ $\left(\begin{array}{rr} 1 & 0 \\ 0 & 1 \end{array}\right)$ & $5$ & $6$ \\ \end{tabular} sage: latex(table(a, frame=True, align='center')) \begin{tabular}{|c|c|c|} \hline $\sin(x)$ & $x$ & text \\ \hline $1$ & $34342$ & $3$ \\ \hline $\left(\begin{array}{rr} 1 & 0 \\ 0 & 1 \end{array}\right)$ & $5$ & $6$ \\ \hline \end{tabular} """
return ""
else: head_col_char = '|' else: else:
# table header # first row else '$' + latex(x).strip() + '$' for x in rows[0]) # other rows else '$' + latex(x).strip() + '$' for x in row)
def _html_(self): r""" HTML representation of a table.
Strings of html will be parsed for math inside dollar and double-dollar signs. 2D graphics will be displayed in the cells. Expressions will be latexed.
The ``align`` option for tables is ignored in HTML output. Specifying ``header_column=True`` may not have any visible effect in the Sage notebook, depending on the version of the notebook.
OUTPUT:
A :class:`~sage.misc.html.HtmlFragment` instance.
EXAMPLES::
sage: T = table([[r'$\sin(x)$', '$x$', 'text'], [1,34342,3], [identity_matrix(2),5,6]]) sage: T._html_() <div.../div> sage: print(T._html_()) <div class="notruncate"> <table class="table_form"> <tbody> <tr class ="row-a"> <td><script type="math/tex">\sin(x)</script></td> <td><script type="math/tex">x</script></td> <td>text</td> </tr> <tr class ="row-b"> <td><script type="math/tex">1</script></td> <td><script type="math/tex">34342</script></td> <td><script type="math/tex">3</script></td> </tr> <tr class ="row-a"> <td><script type="math/tex">\left(\begin{array}{rr} 1 & 0 \\ 0 & 1 \end{array}\right)</script></td> <td><script type="math/tex">5</script></td> <td><script type="math/tex">6</script></td> </tr> </tbody> </table> </div>
Note that calling ``html(table(...))`` has the same effect as calling ``table(...)._html_()``::
sage: T = table([["$x$", r"$\sin(x)$"]] + [(x,n(sin(x), digits=2)) for x in [0..3]], header_row=True, frame=True) sage: T +-----+-----------+ | $x$ | $\sin(x)$ | +=====+===========+ | 0 | 0.00 | +-----+-----------+ | 1 | 0.84 | +-----+-----------+ | 2 | 0.91 | +-----+-----------+ | 3 | 0.14 | +-----+-----------+ sage: print(html(T)) <div class="notruncate"> <table border="1" class="table_form"> <tbody> <tr> <th><script type="math/tex">x</script></th> <th><script type="math/tex">\sin(x)</script></th> </tr> <tr class ="row-a"> <td><script type="math/tex">0</script></td> <td><script type="math/tex">0.00</script></td> </tr> <tr class ="row-b"> <td><script type="math/tex">1</script></td> <td><script type="math/tex">0.84</script></td> </tr> <tr class ="row-a"> <td><script type="math/tex">2</script></td> <td><script type="math/tex">0.91</script></td> </tr> <tr class ="row-b"> <td><script type="math/tex">3</script></td> <td><script type="math/tex">0.14</script></td> </tr> </tbody> </table> </div> """ else: # If the table has < 100 rows, don't truncate the output in the notebook '<div class="notruncate">\n' if len(rows) <= 100 else '<div class="truncate">' , '<table {} class="table_form">\n'.format(frame), '<tbody>\n', ]) # First row:
# Other rows:
def _html_table_row(self, file, row, header=False): r""" Write table row
Helper method used by the :meth:`_html_` method.
INPUT:
- ``file`` -- file-like object. The table row data will be written to it.
- ``row`` -- a list with the same number of entries as each row of the table.
- ``header`` -- bool (default False). If True, treat this as a header row, using ``<th>`` instead of ``<td>``.
OUTPUT:
This method returns nothing. All output is written to ``file``.
Strings are written verbatim unless they seem to be LaTeX code, in which case they are enclosed in a ``script`` tag appropriate for MathJax. Sage objects are printed using their LaTeX representations.
EXAMPLES::
sage: T = table([['a', 'bb', 'ccccc'], [10, -12, 0], [1, 2, 3]]) sage: from six import StringIO sage: s = StringIO() sage: T._html_table_row(s, ['a', 2, '$x$']) sage: print(s.getvalue()) <td>a</td> <td><script type="math/tex">2</script></td> <td><script type="math/tex">x</script></td> """
row = list(row) row = [row]
first_column_tag = '<th class="ch">%s</th>\n' if header else '<td class="ch">%s</td>\n' else:
# First entry of row: file.write(first_column_tag % entry.show(linkmode = True)) else:
# Other entries: file.write(column_tag % row[column].show(linkmode = True)) else: |