def _shape_to_string(shape, axes=None):
    """

    Return an annotated string describing the given data shape.

    For example, if shape=(3,4) and axes=('columns', 'rows')
    the string "3 rows x 4 columns" will be returned. If axes
    is not given, the string "3 x 4" will be returned.

    If shape=(2,3,4) and axes=('hypercubes','cubes','rows','columns')
    the string "2 cubes x 3 rows x 4 columns" will be returned. The
    axes array is truncated on the left to match.

    :Attributes:

    shape: tuple of ints
        The shape to be described.
    axes: tuple of str, optional
        The names of the axis labels corresponding to the given shape.
        If the axes list is longer that the shape it will be truncated
        on the left.
        If the axes list is not given, or is too short, the shape will
        be described without labels.

    :Returns:

    description: str
        A human readable description of the given shape.
        For example, "3 cubes x 4 rows x 6 columns".

    """
    if axes is not None:
        if len(shape) == 1:
            strg = "%d %s" % (int(shape[0]), axes[-1])
        elif len(axes) >= len(shape):
            strg = ''
            iishift = len(axes) - len(shape)
            for ii in range(0,len(shape)):
                jj = ii + iishift
                if ii < len(shape) - 1:
                    strg += "%d %s x " % (int(shape[ii]), axes[jj])
                else:
                    strg += "%d %s" % (int(shape[ii]), axes[jj])
        else:
            # A much simpler implementation when there are inadequate
            # axis labels
            strg = ' x '.join(str(s) for s in shape)
    else:
        # A much simpler implementation when there are no axis labels
        strg = ' x '.join(str(s) for s in shape)
    return strg