Skip to content

Recommended way of comparing Tables (report_diff_values) depends on terminal width. #14010

Closed

Description

@erykoff

Description

The Table docs (5.1.1) recommend using report_diff_values to compare two tables: https://docs.astropy.org/en/stable/table/operations.html#table-diff

Unfortunately, for wide tables with many columns this comparison only compares the truncated table, and this depends on the terminal width.

For example, the following code has two different tables:

from astropy.table import Table
from astropy.utils.diff import report_diff_values


table1 = Table(
    [[0], [0], [0], [0], [0], [0], [0], [0], [0], ["test"]],
    names=[
        "column1",
        "column2",
        "column3",
        "column4",
        "column5",
        "column6",
        "column7",
        "column8",
        "column9",
        "column10",
    ]
)

table2 = Table(
    [[0], [0], [0], [0], [0], [0], [0], [0], [0], [0], ["test"]],
    names=[
        "column1",
        "column2",
        "column3",
        "column4",
        "column4b",
        "column5",
        "column6",
        "column7",
        "column8",
        "column9",
        "column10",
    ]
)

print(report_diff_values(table1, table2))

And whether these compare equal or not (returning True or False) depends on how wide your terminal is when you run the code.

My colleague @timj was told by @taldcroft to use the following code to compare two tables that does not depend on the terminal width:

table1.pformat_all() == table2.pformat_all()

So the documentation should be updated to make this more robust suggestion. However, I want to note that neither of these comparisons will check that the datatypes are equal in the two version of the table.

Expected behavior

Actual behavior

Steps to Reproduce

  1. [First Step]
  2. [Second Step]
  3. [and so on...]
# Put your Python code snippet here.

System Details

Activity

  1. timj commented on Nov 15, 2022

    @timj
    Contributor

    On a related note, does Table support __eq__ directly?

  2. erykoff commented on Nov 15, 2022

    @erykoff
    ContributorAuthor

    Not really. If you try it says:

    table/table.py:3476: FutureWarning: elementwise == comparison failed and returning scalar instead; this will raise an error or perform elementwise comparison in the future.
      result = self.as_array() == other
    
  3. neutrinoceros commented on Jan 8, 2024

    @neutrinoceros
    Contributor

    I think there are multiple aspects that can be addressed here:

    1. report_diff_values's docstring already hints that it really works by comparing string representations, but this could be clarified.
    2. https://docs.astropy.org/en/stable/table/operations.html#table-diff should explicitly warn about it, and state that more finesse is needed to compare large table
    3. table1.pformat_all() == table2.pformat_all() is indeed more robust, but doesn't replace report_diff_values fully (just the return value). I think a possibly nice way to stay in line with historical methods would be to make report_diff_values more extensible by allowing users to set custom string conversion methods. Something like
    report_diff_values(table1, table2, str_fun=lambda t: "\n".join(t.pformat_all()))

    Another possible solution would be to special case tables to disallow repr truncation, but that would add complexity and could prove insufficient in case another type ever needs similar treatment.

    I think points 1 and 2 are rather uncontroversial and low effort, but it'd still be nicer to solve them alongside point 3, so that new documentation is in line with the (proposed) new API.
    @taldcroft , any thoughts on my proposed extended API ?

    Finally:

    I want to note that neither of these comparisons will check that the datatypes are equal in the two version of the table.

    I think this could already be made clearer by solving point 1.

  4. taldcroft commented on Jan 8, 2024

    @taldcroft
    Member

    @neutrinoceros - one small step forward would be to make report_diff_values work as intended and ignore the terminal size.

    A somewhat radical way to do this is changing Table.pformat so that by default it has no limit on lines or width. The point here is that Table.pprint() is intended as an interactive function that should respect the terminal size, but for pformat() I can't think of a good reason it should care about the terminal. Of course right now pprint() is basically just calling pformat() so there are some subtleties. Big picture is whether this big change is going to break lots of stuff.

    A much narrower fix would be to update report_diff_values to run within a context manager like below:

    In [12]: t = simple_table(20, 26, masked=True, kinds="ifOS")
    
    In [13]: t.pformat()  # truncated to terminal
    Out[13]: 
    [' a   b       c      d   e   f       g      h   i   j       k     ...  q   r       s      t   u   v       w      x   y   z  ',
     '--- ---- --------- --- --- ---- --------- --- --- ---- --------- ... --- ---- --------- --- --- ---- --------- --- --- ----',
     " --  1.0  {'c': 2}  --   5  5.0        --   h   9   -- {'k': 10} ...  17 17.0        --   t  21   -- {'w': 22}   x  -- 25.0",
     "  2  2.0        --   e   6   --  {'h': 7}   i  -- 10.0 {'l': 11} ...  18   -- {'t': 19}   u  -- 22.0 {'x': 23}  --  26 26.0",
     "  3   --  {'e': 4}   f  --  7.0  {'i': 8}  --  11 11.0        -- ...  -- 19.0 {'u': 20}  --  23 23.0        --   z  27   --",
     '...  ...       ... ... ...  ...       ... ... ...  ...       ... ... ...  ...       ... ... ...  ...       ... ... ...  ...',
     " 18   -- {'t': 19}   u  -- 22.0 {'x': 23}  --  26 26.0        -- ...  -- 34.0 {'J': 35}  --  38 38.0        --   O  42   --",
     " -- 19.0 {'u': 20}  --  23 23.0        --   z  27   -- {'C': 28} ...  35 35.0        --   L  39   -- {'O': 40}   P  -- 43.0",
     " 20 20.0        --   w  24   -- {'z': 25}   A  -- 28.0 {'D': 29} ...  36   -- {'L': 37}   M  -- 40.0 {'P': 41}  --  44 44.0",
     'Length = 20 rows']
    
    In [13.5]: from astropy import conf
    
    In [14]: with conf.set_temp("max_width", -1), conf.set_temp("max_lines", -1):  # no limits
        ...:     out = t.pformat()  # or str(t) which is a very thin wrapper around pformat()
    
    In [15]: len(out)
    Out[15]: 22
    
    In [16]: len(out[0])
    Out[16]: 146
    
  5. neutrinoceros commented on Jan 8, 2024

    @neutrinoceros
    Contributor

    A much narrower fix would be to update report_diff_values to run within a context manager like below:

    wow, this idea crossed my mind earlier today but I had no idea that we already had the api to do it so elegantly. I think this approach could be viable for a bug fix release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions