mirror of
				https://github.com/KevinMidboe/tableprint.git
				synced 2025-10-29 18:00:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			331 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# -*- coding: utf-8 -*-
 | 
						|
"""
 | 
						|
Tableprint
 | 
						|
 | 
						|
A module to print and display formatted tables of data
 | 
						|
 | 
						|
Usage
 | 
						|
-----
 | 
						|
>>> data = np.random.randn(10, 3)
 | 
						|
>>> headers = ['Column A', 'Column B', 'Column C']
 | 
						|
>>> tableprint.table(data, headers)
 | 
						|
"""
 | 
						|
from __future__ import print_function, unicode_literals
 | 
						|
from metadata import __version__
 | 
						|
from six import string_types
 | 
						|
from collections import namedtuple
 | 
						|
from numbers import Number
 | 
						|
import sys
 | 
						|
import re
 | 
						|
import numpy as np
 | 
						|
 | 
						|
__all__ = ('table', 'header', 'row', 'hr', 'top', 'bottom',
 | 
						|
           'banner', 'dataframe', 'humantime', 'styles')
 | 
						|
 | 
						|
# set up table styles
 | 
						|
LineStyle = namedtuple('LineStyle', ('begin', 'hline', 'sep', 'end'))
 | 
						|
TableStyle = namedtuple('TableStyle', ('top', 'below_header', 'bottom', 'row'))
 | 
						|
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(' ', '', ' ', ' '),
 | 
						|
    ),
 | 
						|
    'round': TableStyle(
 | 
						|
        top=LineStyle('╭', '─', '┬', '╮'),
 | 
						|
        below_header=LineStyle('├', '─', '┼', '┤'),
 | 
						|
        bottom=LineStyle('╰', '─', '┴', '╯'),
 | 
						|
        row=LineStyle('│', '', '│', '│'),
 | 
						|
    ),
 | 
						|
    'banner': TableStyle(
 | 
						|
        top=LineStyle('╒', '═', '╤', '╕'),
 | 
						|
        below_header=LineStyle("╘", "═", "╧", "╛"),
 | 
						|
        bottom=LineStyle("╘", "═", "╧", "╛"),
 | 
						|
        row=LineStyle('│', '', '│', '│'),
 | 
						|
    ),
 | 
						|
    'block': TableStyle(
 | 
						|
        top=LineStyle('◢', '■', '■', '◣'),
 | 
						|
        below_header=LineStyle(' ', '━', '━', ' '),
 | 
						|
        bottom=LineStyle('◥', '■', '■', '◤'),
 | 
						|
        row=LineStyle(' ', '', ' ', ' '),
 | 
						|
    ),
 | 
						|
}
 | 
						|
STYLE = 'round'
 | 
						|
WIDTH = 11
 | 
						|
FMT = '5g'
 | 
						|
 | 
						|
 | 
						|
def table(data, headers=None, format_spec=FMT, width=WIDTH, style=STYLE, out=sys.stdout):
 | 
						|
    """Print a table with the given data
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    data : array_like
 | 
						|
        An (m x n) array containing the data to print (m rows of n columns)
 | 
						|
 | 
						|
    headers : list, optional
 | 
						|
        A list of n strings consisting of the header of each of the n columns (Default: None)
 | 
						|
 | 
						|
    format_spec : string, optional
 | 
						|
        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 = styles[style]
 | 
						|
 | 
						|
    # Initialize with a hr or the header
 | 
						|
    tablestr = [hr(ncols, width, tablestyle.top)] \
 | 
						|
        if headers is None else [header(headers, width, style)]
 | 
						|
 | 
						|
    # parse each row
 | 
						|
    tablestr += [row(d, width, format_spec, style) for d in data]
 | 
						|
 | 
						|
    # only add the final border if there was data in the table
 | 
						|
    if len(data) > 0:
 | 
						|
        tablestr += [hr(ncols, width, tablestyle.bottom)]
 | 
						|
 | 
						|
    # print the table
 | 
						|
    out.write('\n'.join(tablestr) + '\n')
 | 
						|
    out.flush()
 | 
						|
 | 
						|
 | 
						|
def header(headers, width=WIDTH, style=STYLE, add_hr=True):
 | 
						|
    """Returns a formatted row of column header strings
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    headers : list of strings
 | 
						|
        A list of n strings, the column headers
 | 
						|
 | 
						|
    width : int
 | 
						|
        The width of each column (Default: 11)
 | 
						|
 | 
						|
    style : string or tuple, optional
 | 
						|
        A formatting style (see styles)
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    headerstr : string
 | 
						|
        A string consisting of the full header row to print
 | 
						|
    """
 | 
						|
    tablestyle = styles[style]
 | 
						|
 | 
						|
    # string formatter
 | 
						|
    data = map(lambda x: ('{:^%d}' % (width + _ansi_len(x))).format(x), headers)
 | 
						|
 | 
						|
    # build the formatted str
 | 
						|
    headerstr = _format_line(data, tablestyle.row)
 | 
						|
 | 
						|
    if add_hr:
 | 
						|
        upper = hr(len(headers), width, tablestyle.top)
 | 
						|
        lower = hr(len(headers), width, tablestyle.below_header)
 | 
						|
        headerstr = '\n'.join([upper, headerstr, lower])
 | 
						|
 | 
						|
    return headerstr
 | 
						|
 | 
						|
 | 
						|
def row(values, width=WIDTH, format_spec=FMT, style=STYLE):
 | 
						|
    """Returns a formatted row of data
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    values : array_like
 | 
						|
        An iterable array of data (numbers or strings), each value is printed in a separate column
 | 
						|
 | 
						|
    width : int
 | 
						|
        The width of each column (Default: 11)
 | 
						|
 | 
						|
    format_spec : string
 | 
						|
        The precision format string used to format numbers in the values array (Default: '5g')
 | 
						|
 | 
						|
    style : namedtuple, optional
 | 
						|
        A line formatting style
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    rowstr : string
 | 
						|
        A string consisting of the full row of data to print
 | 
						|
    """
 | 
						|
    tablestyle = styles[style]
 | 
						|
 | 
						|
    assert isinstance(format_spec, string_types) | (type(format_spec) is list), \
 | 
						|
        "format_spec must be a string or list of strings"
 | 
						|
 | 
						|
    if isinstance(format_spec, string_types):
 | 
						|
        format_spec = [format_spec] * len(list(values))
 | 
						|
 | 
						|
    # mapping function for string formatting
 | 
						|
    def mapdata(val):
 | 
						|
 | 
						|
        # unpack
 | 
						|
        datum, prec = val
 | 
						|
 | 
						|
        if isinstance(datum, string_types):
 | 
						|
            return ('{:>%i}' % (width + _ansi_len(datum))).format(datum)
 | 
						|
 | 
						|
        elif isinstance(datum, Number):
 | 
						|
            return ('{:>%i.%s}' % (width, prec)).format(datum)
 | 
						|
 | 
						|
        else:
 | 
						|
            raise ValueError('Elements in the values array must be strings, ints, or floats')
 | 
						|
 | 
						|
    # string formatter
 | 
						|
    data = map(mapdata, zip(values, format_spec))
 | 
						|
 | 
						|
    # build the row string
 | 
						|
    return _format_line(data, tablestyle.row)
 | 
						|
 | 
						|
 | 
						|
def hr(n, width=WIDTH, linestyle=LineStyle('', '─', '─', '')):
 | 
						|
    """Returns a formatted string used as a border between table rows
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    n : int
 | 
						|
        The number of columns in the table
 | 
						|
 | 
						|
    width : int
 | 
						|
        The width of each column (Default: 11)
 | 
						|
 | 
						|
    linestyle : tuple
 | 
						|
        A LineStyle namedtuple containing the characters for (begin, hr, sep, end).
 | 
						|
        (Default: ('', '─', '─', ''))
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    rowstr : string
 | 
						|
        A string consisting of the row border to print
 | 
						|
    """
 | 
						|
    hrstr = linestyle.sep.join([('{:%s^%i}' % (linestyle.hline, width)).format('')] * n)
 | 
						|
    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=styles[style].top)
 | 
						|
 | 
						|
 | 
						|
def bottom(n, width=WIDTH, style=STYLE):
 | 
						|
    """Prints the top row of a table"""
 | 
						|
    return hr(n, width, linestyle=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 minimum width of the banner (Default: 30)
 | 
						|
 | 
						|
    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], max(width, len(message)), 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):
 | 
						|
    """Converts a time in seconds to a reasonable human readable time
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    t : float
 | 
						|
        The number of seconds
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    time : string
 | 
						|
        The human readable formatted value of the given time
 | 
						|
    """
 | 
						|
    try:
 | 
						|
        t = float(t)
 | 
						|
    except (ValueError, TypeError):
 | 
						|
        raise ValueError("Input must be numeric")
 | 
						|
 | 
						|
    # weeks
 | 
						|
    if t >= 7*60*60*24:
 | 
						|
        weeks = np.floor(t / (7.*60.*60.*24.))
 | 
						|
        timestr = "{:g} weeks, ".format(weeks) + humantime(t % (7*60*60*24))
 | 
						|
 | 
						|
    # days
 | 
						|
    elif t >= 60*60*24:
 | 
						|
        days = np.floor(t / (60.*60.*24.))
 | 
						|
        timestr = "{:g} days, ".format(days) + humantime(t % (60*60*24))
 | 
						|
 | 
						|
    # hours
 | 
						|
    elif t >= 60*60:
 | 
						|
        hours = np.floor(t / (60.*60.))
 | 
						|
        timestr = "{:g} hours, ".format(hours) + humantime(t % (60*60))
 | 
						|
 | 
						|
    # minutes
 | 
						|
    elif t >= 60:
 | 
						|
        minutes = np.floor(t / 60.)
 | 
						|
        timestr = "{:g} min., ".format(minutes) + humantime(t % 60)
 | 
						|
 | 
						|
    # seconds
 | 
						|
    elif (t >= 1) | (t == 0):
 | 
						|
        timestr = "{:g} s".format(t)
 | 
						|
 | 
						|
    # milliseconds
 | 
						|
    elif t >= 1e-3:
 | 
						|
        timestr = "{:g} ms".format(t*1e3)
 | 
						|
 | 
						|
    # microseconds
 | 
						|
    elif t >= 1e-6:
 | 
						|
        timestr = "{:g} \u03BCs".format(t*1e6)
 | 
						|
 | 
						|
    # nanoseconds or smaller
 | 
						|
    else:
 | 
						|
        timestr = "{:g} ns".format(t*1e9)
 | 
						|
 | 
						|
    return timestr
 | 
						|
 | 
						|
 | 
						|
def _ansi_len(string):
 | 
						|
    """Extra length due to any ANSI sequences in the string."""
 | 
						|
    return len(string) - len(re.compile(r'\x1b[^m]*m').sub('', string))
 | 
						|
 | 
						|
 | 
						|
def _format_line(data, linestyle):
 | 
						|
    """Formats a list of elements using the given line style"""
 | 
						|
    return linestyle.begin + linestyle.sep.join(data) + linestyle.end
 |