Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions datacompy/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import logging
from abc import ABC, abstractmethod
from collections import Counter
from collections.abc import Sequence
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, List, TypedDict

Expand Down Expand Up @@ -532,6 +533,79 @@ def _resolve_template_path(template_name: str) -> tuple[str, str]:
)


#: Spaces between adjacent columns of a report table.
TABLE_GUTTER = " "


def fixed_width_table(
headers: Sequence[str],
rows: Sequence[Sequence[Any]],
align: str = "",
) -> str:
"""Lay out a plain text table, sizing every column to its widest cell.

Exposed to templates as ``fixed_width_table``. Cells are stringified but not
otherwise formatted, so the template stays in charge of how a number is
presented and this function stays in charge only of how wide the column is.
That split is what keeps the header rule, the separator, and the body
aligned no matter how long a dataset name or dtype turns out to be.

Parameters
----------
headers : sequence of str
Column headings. Also the minimum width of each column.
rows : sequence of sequence
Body rows. Each row must have one cell per heading.
align : str, optional
One character per column, ``"l"`` for left and ``"r"`` for right.
Columns beyond the end of the string, and an omitted *align*, default
to left.

Returns
-------
str
The heading row, a separator row of dashes, and one row per entry,
newline separated and free of trailing whitespace.

Raises
------
ValueError
If a row does not have one cell per heading.

Examples
--------
>>> print(fixed_width_table(["Name", "N"], [["ab", 1], ["cdefg", 22]], "lr"))
Name N
----- --
ab 1
cdefg 22
"""
body = [[str(cell) for cell in row] for row in rows]
for index, row in enumerate(body):
if len(row) != len(headers):
raise ValueError(
f"row {index} has {len(row)} cells but there are {len(headers)} headers"
)

widths = [
max(len(header), *(len(row[column]) for row in body)) if body else len(header)
for column, header in enumerate(headers)
]

def lay_out(cells: Sequence[str]) -> str:
padded = [
cell.rjust(width)
if align[column : column + 1] == "r"
else cell.ljust(width)
for column, (cell, width) in enumerate(zip(cells, widths, strict=True))
]
return TABLE_GUTTER.join(padded).rstrip()

lines = [lay_out(headers), TABLE_GUTTER.join("-" * width for width in widths)]
lines.extend(lay_out(row) for row in body)
return "\n".join(lines)


def render(template_name: str, **context: Any) -> str:
"""Render a template using Jinja2.

Expand Down Expand Up @@ -563,7 +637,11 @@ def render(template_name: str, **context: Any) -> str:
autoescape=select_autoescape(),
trim_blocks=True,
lstrip_blocks=True,
# ``do`` lets the template assemble table rows in a loop, so cell
# formatting stays next to the table it belongs to.
extensions=["jinja2.ext.do"],
)
env.globals["fixed_width_table"] = fixed_width_table
template = env.get_template(template_file)
return template.render(**context).strip()

Expand Down
45 changes: 24 additions & 21 deletions datacompy/templates/report_template.j2
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ DataComPy Comparison
DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
{{ '%-10s %8d %5d'|format(df1_name, df1_shape[1], df1_shape[0]) }}
{{ '%-10s %8d %5d'|format(df2_name, df2_shape[1], df2_shape[0]) }}
{{ fixed_width_table(
['DataFrame', 'Columns', 'Rows'],
[[df1_name, '{:,}'.format(df1_shape[1]), '{:,}'.format(df1_shape[0])],
[df2_name, '{:,}'.format(df2_shape[1]), '{:,}'.format(df2_shape[0])]],
'lrr') }}


Column Summary
Expand Down Expand Up @@ -42,27 +43,29 @@ Total number of values which compare unequal: {{ "{:,}".format(column_comparison
Columns with Unequal Values or Types
------------------------------------

{% set headers = ['Column',
mismatch_stats.df1_name + ' dtype',
mismatch_stats.df2_name + ' dtype',
'# Unequal',
'Max Diff',
'# Null Diff',
'Rel Tol',
'Abs Tol'] %}
{{ '%-20s %-15s %-15s %10s %10s %12s %10s %10s'|format(headers[0], headers[1], headers[2], headers[3], headers[4], headers[5], headers[6], headers[7]) }}
{{ '-' * 20 }} {{ '-' * 15 }} {{ '-' * 15 }} {{ '-' * 10 }} {{ '-' * 10 }} {{ '-' * 12 }} {{ '-' * 10 }} {{ '-' * 10 }}
{% set stat_rows = [] %}
{% for stat in mismatch_stats.stats %}
{{ '%-20s %-15s %-15s %10d %10.4f %12d %10.4f %10.4f'|format(
stat.column[:18] + ('...' if stat.column|length > 18 else ''),
{% do stat_rows.append([
stat.column if stat.column|length <= 20 else stat.column[:17] ~ '...',
stat.dtype1,
stat.dtype2,
stat.unequal_cnt,
stat.max_diff,
stat.null_diff,
stat.rel_tol,
stat.abs_tol) }}
'{:,}'.format(stat.unequal_cnt),
'%.4f'|format(stat.max_diff),
'{:,}'.format(stat.null_diff),
'%.4f'|format(stat.rel_tol),
'%.4f'|format(stat.abs_tol)]) %}
{% endfor %}
{{ fixed_width_table(
['Column',
mismatch_stats.df1_name + ' dtype',
mismatch_stats.df2_name + ' dtype',
'# Unequal',
'Max Diff',
'# Null Diff',
'Rel Tol',
'Abs Tol'],
stat_rows,
'lllrrrrr') }}

{% if mismatch_stats.has_samples %}

Expand Down
6 changes: 3 additions & 3 deletions tests/snapshots/pandas_duplicates.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
df1 2 3
df2 2 3
--------- ------- ----
df1 2 3
df2 2 3


Column Summary
Expand Down
6 changes: 3 additions & 3 deletions tests/snapshots/pandas_no_mismatches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
left 3 3
right 3 3
--------- ------- ----
left 3 3
right 3 3


Column Summary
Expand Down
12 changes: 6 additions & 6 deletions tests/snapshots/pandas_on_index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
df1 1 3
df2 1 3
--------- ------- ----
df1 1 3
df2 1 3


Column Summary
Expand Down Expand Up @@ -41,9 +41,9 @@ Total number of values which compare unequal: 1
Columns with Unequal Values or Types
------------------------------------

Column df1 dtype df2 dtype # Unequal Max Diff # Null Diff Rel Tol Abs Tol
-------------------- --------------- --------------- ---------- ---------- ------------ ---------- ----------
val int64 int64 1 79.0000 0 0.0000 0.0000
Column df1 dtype df2 dtype # Unequal Max Diff # Null Diff Rel Tol Abs Tol
------ --------- --------- --------- -------- ----------- ------- -------
val int64 int64 1 79.0000 0 0.0000 0.0000


Sample Rows with Unequal Values
Expand Down
12 changes: 6 additions & 6 deletions tests/snapshots/pandas_sample_count_zero.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
df1 2 3
df2 2 3
--------- ------- ----
df1 2 3
df2 2 3


Column Summary
Expand Down Expand Up @@ -41,6 +41,6 @@ Total number of values which compare unequal: 3
Columns with Unequal Values or Types
------------------------------------

Column df1 dtype df2 dtype # Unequal Max Diff # Null Diff Rel Tol Abs Tol
-------------------- --------------- --------------- ---------- ---------- ------------ ---------- ----------
val int64 int64 3 3.0000 0 0.0000 0.0000
Column df1 dtype df2 dtype # Unequal Max Diff # Null Diff Rel Tol Abs Tol
------ --------- --------- --------- -------- ----------- ------- -------
val int64 int64 3 3.0000 0 0.0000 0.0000
6 changes: 3 additions & 3 deletions tests/snapshots/pandas_unique_columns.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
df1 3 2
df2 3 2
--------- ------- ----
df1 3 2
df2 3 2


Column Summary
Expand Down
6 changes: 3 additions & 3 deletions tests/snapshots/pandas_unique_rows.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
df1 2 3
df2 2 3
--------- ------- ----
df1 2 3
df2 2 3


Column Summary
Expand Down
14 changes: 7 additions & 7 deletions tests/snapshots/pandas_with_mismatches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
df1 3 3
df2 3 3
--------- ------- ----
df1 3 3
df2 3 3


Column Summary
Expand Down Expand Up @@ -41,10 +41,10 @@ Total number of values which compare unequal: 2
Columns with Unequal Values or Types
------------------------------------

Column df1 dtype df2 dtype # Unequal Max Diff # Null Diff Rel Tol Abs Tol
-------------------- --------------- --------------- ---------- ---------- ------------ ---------- ----------
score float64 float64 1 0.5000 0 0.0000 0.0000
val int64 int64 1 79.0000 0 0.0000 0.0000
Column df1 dtype df2 dtype # Unequal Max Diff # Null Diff Rel Tol Abs Tol
------ --------- --------- --------- -------- ----------- ------- -------
score float64 float64 1 0.5000 0 0.0000 0.0000
val int64 int64 1 79.0000 0 0.0000 0.0000


Sample Rows with Unequal Values
Expand Down
6 changes: 3 additions & 3 deletions tests/snapshots/pandas_with_tolerances.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
df1 2 2
df2 2 2
--------- ------- ----
df1 2 2
df2 2 2


Column Summary
Expand Down
6 changes: 3 additions & 3 deletions tests/snapshots/polars_no_mismatches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
left 2 3
right 2 3
--------- ------- ----
left 2 3
right 2 3


Column Summary
Expand Down
6 changes: 3 additions & 3 deletions tests/snapshots/polars_unique_columns.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
df1 3 2
df2 3 2
--------- ------- ----
df1 3 2
df2 3 2


Column Summary
Expand Down
6 changes: 3 additions & 3 deletions tests/snapshots/polars_unique_rows.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
df1 2 3
df2 2 3
--------- ------- ----
df1 2 3
df2 2 3


Column Summary
Expand Down
14 changes: 7 additions & 7 deletions tests/snapshots/polars_with_mismatches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ DataFrame Summary
-----------------

DataFrame Columns Rows
------------------------
df1 3 3
df2 3 3
--------- ------- ----
df1 3 3
df2 3 3


Column Summary
Expand Down Expand Up @@ -41,10 +41,10 @@ Total number of values which compare unequal: 2
Columns with Unequal Values or Types
------------------------------------

Column df1 dtype df2 dtype # Unequal Max Diff # Null Diff Rel Tol Abs Tol
-------------------- --------------- --------------- ---------- ---------- ------------ ---------- ----------
score Float64 Float64 1 0.5000 0 0.0000 0.0000
val Int64 Int64 1 79.0000 0 0.0000 0.0000
Column df1 dtype df2 dtype # Unequal Max Diff # Null Diff Rel Tol Abs Tol
------ --------- --------- --------- -------- ----------- ------- -------
score Float64 Float64 1 0.5000 0 0.0000 0.0000
val Int64 Int64 1 79.0000 0 0.0000 0.0000


Sample Rows with Unequal Values
Expand Down
Loading
Loading