def print_table(self, max_rows=20, max_columns=6, output=sys.stdout, max_column_width=20, locale=None, max_precision=3): """ Print a text-based view of the data in this table. The output of this method is GitHub Flavored Markdown (GFM) compatible. :param max_rows: The maximum number of rows to display before truncating the data. This defaults to :code:`20` to prevent accidental printing of the entire table. Pass :code:`None` to disable the limit. :param max_columns: The maximum number of columns to display before truncating the data. This defaults to :code:`6` to prevent wrapping in most cases. Pass :code:`None` to disable the limit. :param output: A file-like object to print to. :param max_column_width: Truncate all columns to at most this width. The remainder will be replaced with ellipsis. :param locale: Provide a locale you would like to be used to format the output. By default it will use the system's setting. :max_precision: Puts a limit on the maximum precision displayed for number types. Numbers with lesser precision won't be affected. This defaults to :code:`3`. Pass :code:`None` to disable limit. """ if max_rows is None: max_rows = len(self._rows) if max_columns is None: max_columns = len(self._columns) if max_precision is None: max_precision = float('inf') ellipsis = config.get_option('ellipsis_chars') h_line = config.get_option('horizontal_line_char') v_line = config.get_option('vertical_line_char') locale = locale or config.get_option('default_locale') rows_truncated = max_rows < len(self._rows) columns_truncated = max_columns < len(self._column_names) column_names = [] for column_name in self.column_names[:max_columns]: if max_column_width is not None and len( column_name) > max_column_width: column_names.append('%s...' % column_name[:max_column_width - 3]) else: column_names.append(column_name) if columns_truncated: column_names.append(ellipsis) widths = [len(n) for n in column_names] number_formatters = [] formatted_data = [] # Determine correct number of decimal places for each Number column for i, c in enumerate(self._columns): if i >= max_columns: break if isinstance(c.data_type, Number): max_places = utils.max_precision(c[:max_rows]) add_ellipsis = False if max_places > max_precision: add_ellipsis = True max_places = max_precision number_formatters.append( utils.make_number_formatter(max_places, add_ellipsis)) else: number_formatters.append(None) # Format data and display column widths for i, row in enumerate(self._rows): if i >= max_rows: break formatted_row = [] for j, v in enumerate(row): if j >= max_columns: v = ellipsis elif v is None: v = '' elif number_formatters[j] is not None and not math.isinf(v): v = format_decimal(v, format=number_formatters[j], locale=locale) else: v = six.text_type(v) if max_column_width is not None and len(v) > max_column_width: v = '%s...' % v[:max_column_width - 3] if len(v) > widths[j]: widths[j] = len(v) formatted_row.append(v) if j >= max_columns: break formatted_data.append(formatted_row) def write(line): output.write(line + '\n') def write_row(formatted_row): """ Helper function that formats individual rows. """ row_output = [] for j, d in enumerate(formatted_row): # Text is left-justified, all other values are right-justified if isinstance(self._column_types[j], Text): output = ' %s ' % d.ljust(widths[j]) else: output = ' %s ' % d.rjust(widths[j]) row_output.append(output) text = v_line.join(row_output) write('%s%s%s' % (v_line, text, v_line)) # Dashes span each width with '+' character at intersection of # horizontal and vertical dividers. divider = '%(v_line)s %(columns)s %(v_line)s' % { 'h_line': h_line, 'v_line': v_line, 'columns': ' | '.join(h_line * w for w in widths) } # Headers write_row(column_names) write(divider) # Rows for formatted_row in formatted_data: write_row(formatted_row) # Row indicating data was truncated if rows_truncated: write_row([ellipsis for n in column_names])
def print_table(self, max_rows=20, max_columns=6, output=sys.stdout, max_column_width=20, locale=None, max_precision=3): """ Print a text-based view of the data in this table. The output of this method is Github Friendly Markdown (GFM) compatible. :param max_rows: The maximum number of rows to display before truncating the data. This defaults to :code:`20` to prevent accidental printing of the entire table. Pass :code:`None` to disable the limit. :param max_columns: The maximum number of columns to display before truncating the data. This defaults to :code:`6` to prevent wrapping in most cases. Pass :code:`None` to disable the limit. :param output: A file-like object to print to. :param max_column_width: Truncate all columns to at most this width. The remainder will be replaced with ellipsis. :param locale: Provide a locale you would like to be used to format the output. By default it will use the system's setting. :max_precision: Puts a limit on the maximum precision displayed for number types. Numbers with lesser precision won't be affected. This defaults to :code:`3`. Pass :code:`None` to disable limit. """ if max_rows is None: max_rows = len(self._rows) if max_columns is None: max_columns = len(self._columns) if max_precision is None: max_precision = float('inf') ellipsis = config.get_option('ellipsis_chars') h_line = config.get_option('horizontal_line_char') v_line = config.get_option('vertical_line_char') locale = locale or config.get_option('default_locale') rows_truncated = max_rows < len(self._rows) columns_truncated = max_columns < len(self._column_names) column_names = [] for column_name in self.column_names[:max_columns]: if max_column_width is not None and len(column_name) > max_column_width: column_names.append('%s...' % column_name[:max_column_width - 3]) else: column_names.append(column_name) if columns_truncated: column_names.append(ellipsis) widths = [len(n) for n in column_names] number_formatters = [] formatted_data = [] # Determine correct number of decimal places for each Number column for i, c in enumerate(self._columns): if i >= max_columns: break if isinstance(c.data_type, Number): max_places = utils.max_precision(c[:max_rows]) add_ellipsis = False if max_places > max_precision: add_ellipsis = True max_places = max_precision number_formatters.append(utils.make_number_formatter(max_places, add_ellipsis)) else: number_formatters.append(None) # Format data and display column widths for i, row in enumerate(self._rows): if i >= max_rows: break formatted_row = [] for j, v in enumerate(row): if j >= max_columns: v = ellipsis elif v is None: v = '' elif number_formatters[j] is not None: v = format_decimal( v, format=number_formatters[j], locale=locale ) else: v = six.text_type(v) if max_column_width is not None and len(v) > max_column_width: v = '%s...' % v[:max_column_width - 3] if len(v) > widths[j]: widths[j] = len(v) formatted_row.append(v) if j >= max_columns: break formatted_data.append(formatted_row) def write(line): output.write(line + '\n') def write_row(formatted_row): """ Helper function that formats individual rows. """ row_output = [] for j, d in enumerate(formatted_row): # Text is left-justified, all other values are right-justified if isinstance(self._column_types[j], Text): output = ' %s ' % d.ljust(widths[j]) else: output = ' %s ' % d.rjust(widths[j]) row_output.append(output) text = v_line.join(row_output) write('%s%s%s' % (v_line, text, v_line)) # Dashes span each width with '+' character at intersection of # horizontal and vertical dividers. divider = '%(v_line)s %(columns)s %(v_line)s' % { 'h_line': h_line, 'v_line': v_line, 'columns': ' | '.join(h_line * w for w in widths) } # Headers write_row(column_names) write(divider) # Rows for formatted_row in formatted_data: write_row(formatted_row) # Row indicating data was truncated if rows_truncated: write_row([ellipsis for n in column_names])
def print_html(self, max_rows=20, max_columns=6, output=sys.stdout, max_column_width=20, locale=None): """ Print an HTML version of this table. :param max_rows: The maximum number of rows to display before truncating the data. This defaults to :code:`20` to prevent accidental printing of the entire table. Pass :code:`None` to disable the limit. :param max_columns: The maximum number of columns to display before truncating the data. This defaults to :code:`6` to prevent wrapping in most cases. Pass :code:`None` to disable the limit. :param output: A file-like object to print to. Defaults to :code:`sys.stdout`, unless running in Jupyter. (See above.) :param max_column_width: Truncate all columns to at most this width. The remainder will be replaced with ellipsis. :param locale: Provide a locale you would like to be used to format the output. By default it will use the system's setting. """ if max_rows is None: max_rows = len(self._rows) if max_columns is None: max_columns = len(self._columns) ellipsis = config.get_option('ellipsis_chars') locale = locale or config.get_option('default_locale') rows_truncated = max_rows < len(self._rows) columns_truncated = max_columns < len(self._column_names) column_names = list(self._column_names[:max_columns]) if columns_truncated: column_names.append(ellipsis) number_formatters = [] formatted_data = [] # Determine correct number of decimal places for each Number column for i, c in enumerate(self._columns): if i >= max_columns: break if isinstance(c.data_type, Number): max_places = utils.max_precision(c[:max_rows]) number_formatters.append(utils.make_number_formatter(max_places)) else: number_formatters.append(None) # Format data for i, row in enumerate(self._rows): if i >= max_rows: break formatted_row = [] for j, v in enumerate(row): if j >= max_columns: v = ellipsis elif v is None: v = '' elif number_formatters[j] is not None: v = format_decimal( v, format=number_formatters[j], locale=locale ) else: v = six.text_type(v) if max_column_width is not None and len(v) > max_column_width: v = '%s...' % v[:max_column_width - 3] formatted_row.append(v) if j >= max_columns: break formatted_data.append(formatted_row) def write(line): output.write(line + '\n') def write_row(formatted_row): """ Helper function that formats individual rows. """ write('<tr>') for j, d in enumerate(formatted_row): # Text is left-justified, all other values are right-justified if isinstance(self._column_types[j], Text): write('<td style="text-align: left;">%s</td>' % d) else: write('<td style="text-align: right;">%s</td>' % d) write('</tr>') # Header write('<table>') write('<thead>') write('<tr>') for i, col in enumerate(column_names): write('<th>%s</th>' % col) write('</tr>') write('</thead>') write('<tbody>') # Rows for formatted_row in formatted_data: write_row(formatted_row) # Row indicating data was truncated if rows_truncated: write_row([ellipsis for n in column_names]) # Footer write('</tbody>') write('</table>')
def print_bars(self, label_column_name='group', value_column_name='Count', domain=None, width=120, output=sys.stdout, printable=False): """ Print a text-based bar chart based on this table. :param label_column_name: The column containing the label values. Defaults to :code:`group`, which is the default output of :meth:`.Table.pivot` or :meth:`.Table.bins`. :param value_column_name: The column containing the bar values. Defaults to :code:`Count`, which is the default output of :meth:`.Table.pivot` or :meth:`.Table.bins`. :param domain: A 2-tuple containing the minimum and maximum values for the chart's x-axis. The domain must be large enough to contain all values in the column. :param width: The width, in characters, to use for the bar chart. Defaults to :code:`120`. :param output: A file-like object to print to. Defaults to :code:`sys.stdout`. :param printable: If true, only printable characters will be outputed. """ tick_mark = config.get_option('tick_char') horizontal_line = config.get_option('horizontal_line_char') locale = config.get_option('default_locale') if printable: bar_mark = config.get_option('printable_bar_char') zero_mark = config.get_option('printable_zero_line_char') else: bar_mark = config.get_option('bar_char') zero_mark = config.get_option('zero_line_char') y_label = label_column_name label_column = self._columns[label_column_name] # if not isinstance(label_column.data_type, Text): # raise ValueError('Only Text data is supported for bar chart labels.') x_label = value_column_name value_column = self._columns[value_column_name] if not isinstance(value_column.data_type, Number): raise DataTypeError( 'Only Number data is supported for bar chart values.') output = output width = width # Format numbers decimal_places = utils.max_precision(value_column) value_formatter = utils.make_number_formatter(decimal_places) formatted_labels = [] for label in label_column: formatted_labels.append(six.text_type(label)) formatted_values = [] for value in value_column: if value is None: formatted_values.append('-') else: formatted_values.append( format_decimal(value, format=value_formatter, locale=locale)) max_label_width = max(max([len(label) for label in formatted_labels]), len(y_label)) max_value_width = max(max([len(value) for value in formatted_values]), len(x_label)) plot_width = width - (max_label_width + max_value_width + 2) min_value = Min(value_column_name).run(self) max_value = Max(value_column_name).run(self) # Calculate dimensions if domain: x_min = Decimal(domain[0]) x_max = Decimal(domain[1]) if min_value < x_min or max_value > x_max: raise ValueError('Column contains values outside specified domain') else: x_min, x_max = utils.round_limits(min_value, max_value) # All positive if x_min >= 0: x_min = Decimal('0') plot_negative_width = 0 zero_line = 0 plot_positive_width = plot_width - 1 # All negative elif x_max <= 0: x_max = Decimal('0') plot_negative_width = plot_width - 1 zero_line = plot_width - 1 plot_positive_width = 0 # Mixed signs else: spread = x_max - x_min negative_portion = (x_min.copy_abs() / spread) # Subtract one for zero line plot_negative_width = int( ((plot_width - 1) * negative_portion).to_integral_value()) zero_line = plot_negative_width plot_positive_width = plot_width - (plot_negative_width + 1) def project(value): if value >= 0: return plot_negative_width + int( (plot_positive_width * (value / x_max)).to_integral_value()) else: return plot_negative_width - int( (plot_negative_width * (value / x_min)).to_integral_value()) # Calculate ticks ticks = OrderedDict() # First tick ticks[0] = x_min ticks[plot_width - 1] = x_max tick_fractions = [Decimal('0.25'), Decimal('0.5'), Decimal('0.75')] # All positive if x_min >= 0: for fraction in tick_fractions: value = x_max * fraction ticks[project(value)] = value # All negative elif x_max <= 0: for fraction in tick_fractions: value = x_min * fraction ticks[project(value)] = value # Mixed signs else: # Zero tick ticks[zero_line] = Decimal('0') # Halfway between min and 0 value = x_min * Decimal('0.5') ticks[project(value)] = value # Halfway between 0 and max value = x_max * Decimal('0.5') ticks[project(value)] = value decimal_places = utils.max_precision(ticks.values()) tick_formatter = utils.make_number_formatter(decimal_places) ticks_formatted = OrderedDict() for k, v in ticks.items(): ticks_formatted[k] = format_decimal(v, format=tick_formatter, locale=locale) def write(line): output.write(line + '\n') # Chart top top_line = u'%s %s' % (y_label.ljust(max_label_width), x_label.rjust(max_value_width)) write(top_line) # Bars for i, label in enumerate(formatted_labels): value = value_column[i] if value == 0 or value is None: bar_width = 0 elif value > 0: bar_width = project(value) - plot_negative_width elif value < 0: bar_width = plot_negative_width - project(value) label_text = label.ljust(max_label_width) value_text = formatted_values[i].rjust(max_value_width) bar = bar_mark * bar_width if value is not None and value >= 0: gap = (u' ' * plot_negative_width) # All positive if x_min <= 0: bar = gap + zero_mark + bar else: bar = bar + gap + zero_mark else: bar = u' ' * (plot_negative_width - bar_width) + bar # All negative or mixed signs if value is None or x_max > value: bar = bar + zero_mark bar = bar.ljust(plot_width) write('%s %s %s' % (label_text, value_text, bar)) # Axis & ticks axis = horizontal_line * plot_width tick_text = u' ' * width for i, (tick, label) in enumerate(ticks_formatted.items()): # First tick if tick == 0: offset = 0 # Last tick elif tick == plot_width - 1: offset = -(len(label) - 1) else: offset = int(-(len(label) / 2)) pos = (width - plot_width) + tick + offset # Don't print intermediate ticks that would overlap if tick != 0 and tick != plot_width - 1: if tick_text[pos - 1:pos + len(label) + 1] != ' ' * (len(label) + 2): continue tick_text = tick_text[:pos] + label + tick_text[pos + len(label):] axis = axis[:tick] + tick_mark + axis[tick + 1:] write(axis.rjust(width)) write(tick_text)
def print_html(self, max_rows=20, max_columns=6, output=sys.stdout, max_column_width=20, locale=None, max_precision=3): """ Print an HTML version of this table. :param max_rows: The maximum number of rows to display before truncating the data. This defaults to :code:`20` to prevent accidental printing of the entire table. Pass :code:`None` to disable the limit. :param max_columns: The maximum number of columns to display before truncating the data. This defaults to :code:`6` to prevent wrapping in most cases. Pass :code:`None` to disable the limit. :param output: A file-like object to print to. Defaults to :code:`sys.stdout`, unless running in Jupyter. (See above.) :param max_column_width: Truncate all columns to at most this width. The remainder will be replaced with ellipsis. :param locale: Provide a locale you would like to be used to format the output. By default it will use the system's setting. :max_precision: Puts a limit on the maximum precision displayed for number types. Numbers with lesser precision won't be affected. This defaults to :code:`3`. Pass :code:`None` to disable limit. """ if max_rows is None: max_rows = len(self._rows) if max_columns is None: max_columns = len(self._columns) if max_precision is None: max_precision = float('inf') ellipsis = config.get_option('ellipsis_chars') locale = locale or config.get_option('default_locale') rows_truncated = max_rows < len(self._rows) columns_truncated = max_columns < len(self._column_names) column_names = list(self._column_names[:max_columns]) if columns_truncated: column_names.append(ellipsis) number_formatters = [] formatted_data = [] # Determine correct number of decimal places for each Number column for i, c in enumerate(self._columns): if i >= max_columns: break if isinstance(c.data_type, Number): max_places = utils.max_precision(c[:max_rows]) add_ellipsis = False if max_places > max_precision: add_ellipsis = True max_places = max_precision number_formatters.append( utils.make_number_formatter(max_places, add_ellipsis)) else: number_formatters.append(None) # Format data for i, row in enumerate(self._rows): if i >= max_rows: break formatted_row = [] for j, v in enumerate(row): if j >= max_columns: v = ellipsis elif v is None: v = '' elif number_formatters[j] is not None and not math.isinf(v): v = format_decimal(v, format=number_formatters[j], locale=locale) else: v = six.text_type(v) if max_column_width is not None and len(v) > max_column_width: v = '%s...' % v[:max_column_width - 3] formatted_row.append(v) if j >= max_columns: break formatted_data.append(formatted_row) def write(line): output.write(line + '\n') def write_row(formatted_row): """ Helper function that formats individual rows. """ write('<tr>') for j, d in enumerate(formatted_row): # Text is left-justified, all other values are right-justified if isinstance(self._column_types[j], Text): write('<td style="text-align: left;">%s</td>' % d) else: write('<td style="text-align: right;">%s</td>' % d) write('</tr>') # Header write('<table>') write('<thead>') write('<tr>') for i, col in enumerate(column_names): write('<th>%s</th>' % col) write('</tr>') write('</thead>') write('<tbody>') # Rows for formatted_row in formatted_data: write_row(formatted_row) # Row indicating data was truncated if rows_truncated: write_row([ellipsis for n in column_names]) # Footer write('</tbody>') write('</table>')
def print_bars(self, label_column_name='group', value_column_name='Count', domain=None, width=120, output=sys.stdout, printable=False): """ Print a text-based bar chart based on this table. :param label_column_name: The column containing the label values. Defaults to :code:`group`, which is the default output of :meth:`.Table.pivot` or :meth:`.Table.bins`. :param value_column_name: The column containing the bar values. Defaults to :code:`Count`, which is the default output of :meth:`.Table.pivot` or :meth:`.Table.bins`. :param domain: A 2-tuple containing the minimum and maximum values for the chart's x-axis. The domain must be large enough to contain all values in the column. :param width: The width, in characters, to use for the bar chart. Defaults to :code:`120`. :param output: A file-like object to print to. Defaults to :code:`sys.stdout`. :param printable: If true, only printable characters will be outputed. """ tick_mark = config.get_option('tick_char') horizontal_line = config.get_option('horizontal_line_char') locale = config.get_option('default_locale') if printable: bar_mark = config.get_option('printable_bar_char') zero_mark = config.get_option('printable_zero_line_char') else: bar_mark = config.get_option('bar_char') zero_mark = config.get_option('zero_line_char') y_label = label_column_name label_column = self._columns[label_column_name] # if not isinstance(label_column.data_type, Text): # raise ValueError('Only Text data is supported for bar chart labels.') x_label = value_column_name value_column = self._columns[value_column_name] if not isinstance(value_column.data_type, Number): raise DataTypeError('Only Number data is supported for bar chart values.') output = output width = width # Format numbers decimal_places = utils.max_precision(value_column) value_formatter = utils.make_number_formatter(decimal_places) formatted_labels = [] for label in label_column: formatted_labels.append(six.text_type(label)) formatted_values = [] for value in value_column: if value is None: formatted_values.append('-') else: formatted_values.append(format_decimal( value, format=value_formatter, locale=locale )) max_label_width = max(max([len(l) for l in formatted_labels]), len(y_label)) max_value_width = max(max([len(v) for v in formatted_values]), len(x_label)) plot_width = width - (max_label_width + max_value_width + 2) min_value = Min(value_column_name).run(self) max_value = Max(value_column_name).run(self) # Calculate dimensions if domain: x_min = Decimal(domain[0]) x_max = Decimal(domain[1]) if min_value < x_min or max_value > x_max: raise ValueError('Column contains values outside specified domain') else: x_min, x_max = utils.round_limits(min_value, max_value) # All positive if x_min >= 0: x_min = Decimal('0') plot_negative_width = 0 zero_line = 0 plot_positive_width = plot_width - 1 # All negative elif x_max <= 0: x_max = Decimal('0') plot_negative_width = plot_width - 1 zero_line = plot_width - 1 plot_positive_width = 0 # Mixed signs else: spread = x_max - x_min negative_portion = (x_min.copy_abs() / spread) # Subtract one for zero line plot_negative_width = int(((plot_width - 1) * negative_portion).to_integral_value()) zero_line = plot_negative_width plot_positive_width = plot_width - (plot_negative_width + 1) def project(value): if value >= 0: return plot_negative_width + int((plot_positive_width * (value / x_max)).to_integral_value()) else: return plot_negative_width - int((plot_negative_width * (value / x_min)).to_integral_value()) # Calculate ticks ticks = OrderedDict() # First tick ticks[0] = x_min ticks[plot_width - 1] = x_max tick_fractions = [Decimal('0.25'), Decimal('0.5'), Decimal('0.75')] # All positive if x_min >= 0: for fraction in tick_fractions: value = x_max * fraction ticks[project(value)] = value # All negative elif x_max <= 0: for fraction in tick_fractions: value = x_min * fraction ticks[project(value)] = value # Mixed signs else: # Zero tick ticks[zero_line] = Decimal('0') # Halfway between min and 0 value = x_min * Decimal('0.5') ticks[project(value)] = value # Halfway between 0 and max value = x_max * Decimal('0.5') ticks[project(value)] = value decimal_places = utils.max_precision(ticks.values()) tick_formatter = utils.make_number_formatter(decimal_places) ticks_formatted = OrderedDict() for k, v in ticks.items(): ticks_formatted[k] = format_decimal( v, format=tick_formatter, locale=locale ) def write(line): output.write(line + '\n') # Chart top top_line = u'%s %s' % (y_label.ljust(max_label_width), x_label.rjust(max_value_width)) write(top_line) # Bars for i, label in enumerate(formatted_labels): value = value_column[i] if value == 0 or value is None: bar_width = 0 elif value > 0: bar_width = project(value) - plot_negative_width elif value < 0: bar_width = plot_negative_width - project(value) label_text = label.ljust(max_label_width) value_text = formatted_values[i].rjust(max_value_width) bar = bar_mark * bar_width if value is not None and value >= 0: gap = (u' ' * plot_negative_width) # All positive if x_min <= 0: bar = gap + zero_mark + bar else: bar = bar + gap + zero_mark else: bar = u' ' * (plot_negative_width - bar_width) + bar # All negative or mixed signs if value is None or x_max > value: bar = bar + zero_mark bar = bar.ljust(plot_width) write('%s %s %s' % (label_text, value_text, bar)) # Axis & ticks axis = horizontal_line * plot_width tick_text = u' ' * width for i, (tick, label) in enumerate(ticks_formatted.items()): # First tick if tick == 0: offset = 0 # Last tick elif tick == plot_width - 1: offset = -(len(label) - 1) else: offset = int(-(len(label) / 2)) pos = (width - plot_width) + tick + offset # Don't print intermediate ticks that would overlap if tick != 0 and tick != plot_width - 1: if tick_text[pos - 1:pos + len(label) + 1] != ' ' * (len(label) + 2): continue tick_text = tick_text[:pos] + label + tick_text[pos + len(label):] axis = axis[:tick] + tick_mark + axis[tick + 1:] write(axis.rjust(width)) write(tick_text)