Adds support for custom table styles

- Defines a few tablestyles which specify the different box characters
  to use
- Modifies the table, header, and hr functions to take a key that is the
  name of one of these styles (instead of fixed characters)
- Renames frame to dataframe
- Adds some helper functions (top, bottom, and banner) for printing just
  the top or bottom row, or a banner with a given message, using a
  styled table
This commit is contained in:
Niru Maheswaranathan
2016-05-02 19:00:31 -07:00
parent 20db6dca49
commit 0c518018a9

View File

@@ -1,131 +1,158 @@
""" """
Tableprint Tableprint
A module to print and display ASCII formatted tables of data A module to print and display formatted tables of data
Usage Usage
----- -----
>>> data = np.random.randn(10,3) >>> data = np.random.randn(10, 3)
>>> headers = ['Column A', 'Column B', 'Column C'] >>> headers = ['Column A', 'Column B', 'Column C']
>>> tableprint.table(data, headers) >>> tableprint.table(data, headers)
""" """
from __future__ import print_function, unicode_literals from __future__ import print_function, unicode_literals
from six import string_types from six import string_types
from collections import namedtuple
from numbers import Number
import sys
import numpy as np import numpy as np
__all__ = ['table', 'row', 'header', 'hr', 'humantime', 'frame'] __all__ = ['table', 'row', 'header', 'hr', 'humantime', 'dataframe']
__version__ = '0.2.1' __version__ = '0.3.0'
# set up table styles
LineStyle = namedtuple('LineStyle', ('begin', 'hline', 'sep', 'end'))
TableStyle = namedtuple('TableStyle', ('top', 'below_header', 'bottom', 'row'))
DEFAULT_STYLES = {
'grid': TableStyle(
top=LineStyle('+', '-', '+', '+'),
below_header=LineStyle('+', '-', '+', '+'),
bottom=LineStyle('+', '-', '+', '+'),
row=LineStyle('|', '', '|', '|'),
),
'fancy_grid': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle('', '', '', ''),
bottom=LineStyle("", "", "", ""),
row=LineStyle('', '', '', ''),
),
'clean': TableStyle(
top=LineStyle(' ', '', ' ', ' '),
below_header=LineStyle(' ', '', ' ', ' '),
bottom=LineStyle(" ", "", " ", " "),
row=LineStyle(' ', '', ' ', ' '),
),
'banner': TableStyle(
top=LineStyle('', '', '', ''),
below_header=LineStyle("", "", "", ""),
bottom=LineStyle("", "", "", ""),
row=LineStyle('', '', '', ''),
),
}
STYLE = 'fancy_grid'
WIDTH = 11
FMT = '5g'
def table(data, headers, format_spec='5g', column_width=10, def table(data, headers=None, format_spec=FMT, width=WIDTH, style=STYLE, out=sys.stdout):
outer_char='\u2502', corner_char='\u253C', line_char='\u2500'): """Print a table with the given data
"""
Print an ASCII table with the given data
Parameters Parameters
---------- ----------
data : array_like data : array_like
An (m x n) array containing the data to print (m rows of n columns) An (m x n) array containing the data to print (m rows of n columns)
headers : list headers : list, optional
A list of n strings consisting of the header of each of the n columns A list of n strings consisting of the header of each of the n columns (Default: None)
column_width : int, optional
The width of each column in the table (Default: 10)
outer_char : string, optional
The character defining the outer border of the table (Default: '|')
corner_char : string, optional
Printed at the junctions of the table lines (Default: '+')
line_char : string, optional
Character as part of each horizontal rule (Default: '-')
format_spec : string, optional format_spec : string, optional
Format specification for formatting numbers (Default: '5g') Format specification for formatting numbers (Default: '5g')
width : int, optional
The width of each column in the table (Default: 11)
style : string or tuple, optional
A formatting style. (Default: 'fancy_grid')
out : writer, optional
A file handle or object that has write() and flush() methods (Default: sys.stdout)
""" """
ncols = len(data[0]) if headers is None else len(headers)
tablestyle = DEFAULT_STYLES[style]
# get the header string # Initialize with a hr or the header
headerstr = header(headers, column_width=column_width, outer_char=outer_char) tablestr = [hr(ncols, width, tablestyle.top)] \
if headers is None else [header(headers, width, style)]
# parse each row # parse each row
tablestr = [headerstr] + [row(d, column_width=column_width, format_spec=format_spec, tablestr += [row(d, width, format_spec, style) for d in data]
outer_char=outer_char) for d in data]
# only add the final border if there was data in the table # only add the final border if there was data in the table
if len(data) > 0: if len(data) > 0:
tablestr += [hr(len(headers), column_width=column_width, tablestr += [hr(ncols, width, tablestyle.bottom)]
corner_char=corner_char, line_char=line_char)]
# print the table # print the table
print('\n'.join(tablestr)) out.write('\n'.join(tablestr) + '\n')
out.flush()
def header(headers, column_width=10, outer_char='\u2502', add_hr=True): def header(headers, width=WIDTH, style=STYLE, add_hr=True):
""" """Returns a formatted row of column header strings
Returns a formatted ASCII row of column header strings
Parameters Parameters
---------- ----------
headers : list of strings headers : list of strings
A list of n strings, the column headers A list of n strings, the column headers
column_width : int width : int
The width of each column (Default: 10) The width of each column (Default: 11)
outer_char : string style : string or tuple, optional
A character printed at the edges of each column (Default: '|') A formatting style (see DEFAULT_STYLES)
Returns Returns
------- -------
headerstr : string headerstr : string
A string consisting of the full header row to print A string consisting of the full header row to print
""" """
tablestyle = DEFAULT_STYLES[style]
# string formatter # string formatter
fmt = map(lambda x: '{:<' + str(column_width) + '}', headers) data = map(lambda x: ('{:^%d}' % width).format(x), headers)
# build the base string
basestr = (' %s ' % outer_char).join(fmt)
# build the formatted str # build the formatted str
headerstr = outer_char + basestr.format(*headers) + outer_char headerstr = _format_line(data, tablestyle.row)
if add_hr: if add_hr:
hr_string = hr(len(headers), column_width=column_width) upper = hr(len(headers), width, tablestyle.top)
headerstr = '\n'.join([hr_string, headerstr, hr_string]) lower = hr(len(headers), width, tablestyle.below_header)
headerstr = '\n'.join([upper, headerstr, lower])
return headerstr return headerstr
def row(values, column_width=10, format_spec='5g', outer_char='\u2502'): def row(values, width=WIDTH, format_spec=FMT, style=STYLE):
""" """Returns a formatted row of data
Returns a formatted ASCII row of data
Parameters Parameters
---------- ----------
values : array_like values : array_like
An iterable array of data (numbers of strings), each value is printed in a separate column An iterable array of data (numbers of strings), each value is printed in a separate column
column_width : int width : int
The width of each column (Default: 10) The width of each column (Default: 11)
format_spec : string format_spec : string
The precision format string used to format numbers in the values array (Default: '5g') The precision format string used to format numbers in the values array (Default: '5g')
outer_char : string style : namedtuple, optional
A character printed at the edges of each column (Default : '|') A line formatting style
Returns Returns
------- -------
rowstr : string rowstr : string
A string consisting of the full row of data to print A string consisting of the full row of data to print
""" """
tablestyle = DEFAULT_STYLES[style]
assert isinstance(format_spec, string_types) | (type(format_spec) is list), \ assert isinstance(format_spec, string_types) | (type(format_spec) is list), \
"format_spec must be a string or list of strings" "format_spec must be a string or list of strings"
@@ -137,60 +164,92 @@ def row(values, column_width=10, format_spec='5g', outer_char='\u2502'):
def mapdata(val): def mapdata(val):
# unpack # unpack
d, prec = val datum, prec = val
if isinstance(d, string_types): if isinstance(datum, string_types):
return ('{:>%i}' % column_width).format(d) return ('{:>%i}' % width).format(datum)
elif isinstance(d, (int, float, np.integer, np.float)): elif isinstance(datum, Number):
return ('{:>%i.%s}' % (column_width, prec)).format(d) return ('{:>%i.%s}' % (width, prec)).format(datum)
else: else:
raise ValueError('Elements in the values array must be strings, ints, or floats') raise ValueError('Elements in the values array must be strings, ints, or floats')
# string formatter # string formatter
fmt = map(mapdata, zip(values, format_spec)) data = map(mapdata, zip(values, format_spec))
# build the base string # build the row string
basestr = (' %s ' % outer_char).join(fmt) return _format_line(data, tablestyle.row)
# build the formatted string
rowstr = outer_char + basestr + outer_char
return rowstr
def hr(ncols, column_width=10, corner_char='\u253C', line_char='\u2500'): def hr(n, width=WIDTH, linestyle=LineStyle('|', '-', '+', '|')):
""" """Returns a formatted string used as a border between table rows
Returns a formatted string used as a border between table rows
Parameters Parameters
---------- ----------
ncols : int n : int
The number of columns in the table The number of columns in the table
column_width : int width : int
The width of each column (Default: 10) The width of each column (Default: 11)
corner_char : string linestyle : tuple
A character printed at the intersection of column edges and the row border (Default: '+') A LineStyle namedtuple containing the characters for (begin, hr, sep, end).
(Default: ('|', '-', '+', '|'))
line_char : string
A character printed in between column edges, defines the row border (Default: '-')
Returns Returns
------- -------
rowstr : string rowstr : string
A string consisting of the row border to print A string consisting of the row border to print
""" """
hrstr = corner_char.join([('{:%s^%i}' % (line_char, column_width + 2)).format('') for _ in range(ncols)]) hrstr = linestyle.sep.join([('{:%s^%i}' % (linestyle.hline, width)).format('')] * n)
return corner_char + hrstr[1:-1] + corner_char return linestyle.begin + hrstr + linestyle.end
def top(n, width=WIDTH, style=STYLE):
"""Prints the top row of a table"""
return hr(n, width, linestyle=DEFAULT_STYLES[style].top)
def bottom(n, width=WIDTH, style=STYLE):
"""Prints the top row of a table"""
return hr(n, width, linestyle=DEFAULT_STYLES[style].bottom)
def banner(message, width=30, style='banner', out=sys.stdout):
"""Prints a banner message
Parameters
----------
message : string
The message to print in the banner
width : int
The width of each column (Default: 11)
style : string
A line formatting style (Default: 'banner')
out : writer
An object that has write() and flush() methods (Default: sys.stdout)
"""
out.write(header([message], width, style) + '\n')
out.flush()
def dataframe(df, **kwargs):
"""Print table with data from the given pandas DataFrame
Parameters
----------
df : DataFrame
A pandas DataFrame with the table to print
"""
table(np.array(df), list(df.columns), **kwargs)
def humantime(t): def humantime(t):
""" """Converts a time in seconds to a reasonable human readable time
Converts a time in seconds to a reasonable human readable time
Parameters Parameters
---------- ----------
@@ -201,9 +260,7 @@ def humantime(t):
------- -------
time : string time : string
The human readable formatted value of the given time The human readable formatted value of the given time
""" """
try: try:
t = float(t) t = float(t)
except (ValueError, TypeError): except (ValueError, TypeError):
@@ -248,29 +305,6 @@ def humantime(t):
return timestr return timestr
def frame(dataframe, **kwargs): def _format_line(data, linestyle):
""" """Formats a list of elements using the given line style"""
Print an ASCII table using the given pandas DataFrame return linestyle.begin + linestyle.sep.join(data) + linestyle.end
Parameters
----------
dataframe : DataFrame
A pandas DataFrame with consisting of the table to print
column_width : int, optional
The width of each column in the table (Default: 10)
outer_char : string, optional
The character defining the outer border of the table (Default: '|')
corner_char : string, optional
Printed at the junctions of the table lines (Default: '+')
line_char : string, optional
Character as part of each horizontal rule (Default: '-')
format_spec : string, optional
Format specification for formatting numbers (Default: '5g')
"""
table(np.array(dataframe), list(dataframe.columns), **kwargs)