Skip to content
142 changes: 136 additions & 6 deletions xarray/core/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import numpy as np
import pandas as pd

from .duck_array_ops import array_equiv
from .options import OPTIONS
from .pycompat import (
PY2, bytes_type, dask_array_type, unicode_type, zip_longest)
Expand Down Expand Up @@ -411,6 +412,15 @@ def short_dask_repr(array, show_dtype=True):
return 'dask.array<shape=%s, chunksize=%s>' % (array.shape, chunksize)


def short_data_repr(array):
if isinstance(getattr(array, 'variable', array)._data, dask_array_type):
return short_dask_repr(array)
elif array._in_memory or array.size < 1e5:
return short_array_repr(array.values)
else:
return u'[%s values with dtype=%s]' % (array.size, array.dtype)


def array_repr(arr):
# used for DataArray, Variable and IndexVariable
if hasattr(arr, 'name') and arr.name is not None:
Expand All @@ -421,12 +431,7 @@ def array_repr(arr):
summary = [u'<xarray.%s %s(%s)>'
% (type(arr).__name__, name_str, dim_summary(arr))]

if isinstance(getattr(arr, 'variable', arr)._data, dask_array_type):
summary.append(short_dask_repr(arr))
elif arr._in_memory or arr.size < 1e5:
summary.append(short_array_repr(arr.values))
else:
summary.append(u'[%s values with dtype=%s]' % (arr.size, arr.dtype))
summary.append(short_data_repr(arr))

if hasattr(arr, 'coords'):
if arr.coords:
Expand Down Expand Up @@ -463,3 +468,128 @@ def dataset_repr(ds):
summary.append(attrs_repr(ds.attrs))

return u'\n'.join(summary)


def diff_dim_summary(a, b):
if a.dims != b.dims:
ab_dim_summaries = [dim_summary(obj) for obj in (a, b)]
return "Differing dimensions:\n({}) != ({})".format(*ab_dim_summaries)
else:
return ""


def _diff_mapping_repr(a_mapping, b_mapping, compat,
title, summarizer, col_width=None):

def extra_items_repr(extra_keys, vars, ab_side):
extra_repr = [summarizer(k, vars[k], col_width) for k in extra_keys]
if extra_repr:
return ["{} contains more {}:".format(ab_side, title)] + extra_repr
else:
return []

a_keys = set(a_mapping)
b_keys = set(b_mapping)

summary = []

diff_items = []

for k in a_keys & b_keys:
try:
# compare xarray variable
compatible = getattr(a_mapping[k], compat)(b_mapping[k])
is_variable = True
except AttributeError:
# compare attribute value
compatible = a_mapping[k] == b_mapping[k]
is_variable = False

if not compatible:
temp = [summarizer(k, vars[k], col_width)
for vars in (a_mapping, b_mapping)]

if compat == 'identical' and is_variable:
attrs_summary = []

for m in (a_mapping, b_mapping):
attr_s = "\n".join([summarize_attr(ak, av)
for ak, av in m[k].attrs.items()])
attrs_summary.append(attr_s)

temp = ["\n".join([var_s, attr_s]) if attr_s else var_s
for var_s, attr_s in zip(temp, attrs_summary)]

diff_items += [ab_side + s[1:]
for ab_side, s in zip(('L', 'R'), temp)]

if diff_items:
summary += ["Differing {}:".format(title)] + diff_items

summary += extra_items_repr(a_keys - b_keys, a_mapping, "Left")
summary += extra_items_repr(b_keys - a_keys, b_mapping, "Right")

return "\n".join(summary)


diff_coords_repr = functools.partial(_diff_mapping_repr, title="coordinates",
summarizer=summarize_coord)


diff_data_vars_repr = functools.partial(_diff_mapping_repr,
title="data variables",
summarizer=summarize_datavar)


diff_attrs_repr = functools.partial(_diff_mapping_repr,
title="attributes",
summarizer=summarize_attr)


def _compat_to_str(compat):
if compat == "equals":
return "equal"
else:
return compat


def diff_array_repr(a, b, compat):
# used for DataArray, Variable and IndexVariable
summary = ["Left and right {} objects are not {}"
.format(type(a).__name__, _compat_to_str(compat))]

if not array_equiv(a.data, b.data):
temp = [wrap_indent(short_array_repr(obj), start=' ')
for obj in (a, b)]
diff_data_repr = [ab_side + "\n" + ab_data_repr
for ab_side, ab_data_repr in zip(('L', 'R'), temp)]
summary += ["Differing values:"] + diff_data_repr

if hasattr(a, 'coords'):
col_width = _calculate_col_width(set(a.coords) | set(b.coords))
summary.append(diff_coords_repr(a.coords, b.coords, compat,
col_width=col_width))

if compat == 'identical':
summary.append(diff_attrs_repr(a.attrs, b.attrs, compat))

return "\n".join(summary)


def diff_dataset_repr(a, b, compat):
summary = ["Left and right {} objects are not {}"
.format(type(a).__name__, _compat_to_str(compat))]

col_width = _calculate_col_width(
set(_get_col_items(a.variables) + _get_col_items(b.variables)))

summary.append(diff_dim_summary(a, b))
summary.append(diff_coords_repr(a.coords, b.coords, compat,
col_width=col_width))
summary.append(diff_data_vars_repr(a.data_vars, b.data_vars, compat,
col_width=col_width))

if compat == 'identical':
summary.append(diff_attrs_repr(a.attrs, b.attrs, compat))

return "\n".join(summary)
17 changes: 12 additions & 5 deletions xarray/testing.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
"""Testing functions exposed to the user API"""
from __future__ import absolute_import, division, print_function

from io import StringIO

import numpy as np

from xarray.core import duck_array_ops
from xarray.core import formatting


def _decode_string_data(data):
Expand Down Expand Up @@ -49,8 +52,10 @@ def assert_equal(a, b):
import xarray as xr
__tracebackhide__ = True # noqa: F841
assert type(a) == type(b) # noqa
if isinstance(a, (xr.Variable, xr.DataArray, xr.Dataset)):
assert a.equals(b), '{}\n{}'.format(a, b)
if isinstance(a, (xr.Variable, xr.DataArray)):
assert a.equals(b), formatting.diff_array_repr(a, b, 'equals')
elif isinstance(a, xr.Dataset):
assert a.equals(b), formatting.diff_dataset_repr(a, b, 'equals')
else:
raise TypeError('{} not supported by assertion comparison'
.format(type(a)))
Expand All @@ -76,11 +81,13 @@ def assert_identical(a, b):
import xarray as xr
__tracebackhide__ = True # noqa: F841
assert type(a) == type(b) # noqa
if isinstance(a, xr.DataArray):
if isinstance(a, xr.Variable):
assert a.identical(b), formatting.diff_array_repr(a, b, 'identical')
elif isinstance(a, xr.DataArray):
assert a.name == b.name
assert_identical(a._to_temp_dataset(), b._to_temp_dataset())
assert a.identical(b), formatting.diff_array_repr(a, b, 'identical')
elif isinstance(a, (xr.Dataset, xr.Variable)):
assert a.identical(b), '{}\n{}'.format(a, b)
assert a.identical(b), formatting.diff_dataset_repr(a, b, 'identical')
else:
raise TypeError('{} not supported by assertion comparison'
.format(type(a)))
Expand Down