Repository: xgi-org/xgi

Issue:
viz: node_fc_cmap does not work if node_fc is not a NodeStat
Let's say we plot a simple hypergraph

```python
edges = [[1, 2], [1, 2, 3], [2, 3], [3, 4, 5, 6]]
H = xgi.Hypergraph(edges)

# plot node size ~ degree, with shades of green
xgi.draw(H, node_fc=H.nodes.degree, node_fc_cmap=mpl.colormaps["Greens"])
```

Here `H.nodes.degree` is a `NodeStat`, it works:
![Screenshot 2023-06-07 at 12 01 21](https://github.com/xgi-org/xgi/assets/7493360/9980ec10-1134-4dfe-a444-e60788afa249)

But if we replace it by a list with the same values  `H.nodes.degree.aslist()`, the cmap is discarded and back to its default:
![Screenshot 2023-06-07 at 12 04 05](https://github.com/xgi-org/xgi/assets/7493360/2c808c16-9122-466e-94aa-4cde38080b33)

I guess we may have a similar problem with related variables:  `node_ec_cmap`, `dyad_color_cmap`,  `edge_fc_cmap`.


Discussion:
thomasrobiglio: The only error in the test that I was able to reproduce is the one at [this line](https://github.com/xgi-org/xgi/actions/runs/5269655064/jobs/9528091131#step:5:220) (which is present in the [branch](https://github.com/xgi-org/xgi/blob/b1d891a45311878cbed6fca9a86ebb115de63069/xgi/drawing/draw.py#L647) but not present in the [main](https://github.com/xgi-org/xgi/blob/8d2960438a238ee504de78858f752ab73df2a3f5/xgi/drawing/draw.py#L646) 🤯) I guess than the others are then  'consequences' (e.g. the test function does not reach `plt.close()` maybe). Is this possible? 
nwlandry: MVP!!!!! Thanks so much, @thomasrobiglio. That was the issue. I'll add a couple of tests and then undraft this PR.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/xgi-org/xgi/pull/402?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xgi-org) Report
Patch coverage: **`89.28`**% and project coverage change: **`+0.01`** :tada:
> Comparison is base [(`00e14ec`)](https://app.codecov.io/gh/xgi-org/xgi/commit/00e14ec8d422e542c432cb41ed624356ab67c856?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xgi-org) 90.94% compared to head [(`934fdeb`)](https://app.codecov.io/gh/xgi-org/xgi/pull/402?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xgi-org) 90.95%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #402      +/-   ##
==========================================
+ Coverage   90.94%   90.95%   +0.01%     
==========================================
  Files          47       47              
  Lines        3888     3904      +16     
==========================================
+ Hits         3536     3551      +15     
- Misses        352      353       +1     
```


| [Impacted Files](https://app.codecov.io/gh/xgi-org/xgi/pull/402?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xgi-org) | Coverage Δ | |
|---|---|---|
| [xgi/drawing/draw.py](https://app.codecov.io/gh/xgi-org/xgi/pull/402?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xgi-org#diff-eGdpL2RyYXdpbmcvZHJhdy5weQ==) | `77.17% <87.50%> (+0.53%)` | :arrow_up: |
| [xgi/convert.py](https://app.codecov.io/gh/xgi-org/xgi/pull/402?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xgi-org#diff-eGdpL2NvbnZlcnQucHk=) | `94.65% <100.00%> (+0.04%)` | :arrow_up: |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/xgi-org/xgi/pull/402?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xgi-org).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xgi-org).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c66c827..c4cd08c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Changelog
 
+## v0.7.1
+* Fix: Converting from a `SimplicialComplex` to a `Hypergraph` now adds all of its faces to the hypergraph, not just the maximal faces. Added unit tests for converting between `Hypergraph` and `SimplicialComplex` classes [#399](https://github.com/xgi-org/xgi/pull/399) (@thomasrobiglio).
+* Moved the list of contributors from the readthedocs, license, setup, etc. into a dedicated file so that when contributors join or leave, we only update a single file. Renamed `CONTRIBUTING.md` to `HOW_TO_CONTRIBUTE.md`. [#401](https://github.com/xgi-org/xgi/pull/401) (@nwlandry).
+* Added the ability to convert from a simplex dict to a `SimplicialComplex` [#397](https://github.com/xgi-org/xgi/pull/397) (@thomasrobiglio).
+* Updated index.rst to add the `DiHypergraph` class to the quick references [#392](https://github.com/xgi-org/xgi/pull/392) (@thomasrobiglio).
+* Addressed Issue [#393](https://github.com/xgi-org/xgi/issues/393) by adding the ability for `draw_hypergraph()` to handle nodes with non-integer IDs and isolated nodes. [#394](https://github.com/xgi-org/xgi/pull/394) (@thomasrobiglio).
+* Fixed the short description and landing page on PyPI so that it is more readable [#391](https://github.com/xgi-org/xgi/pull/391) (@nwlandry).
 
 ## v0.7
 * Added the functionality to load datasets from the [BiGG database](http://bigg.ucsd.edu/) [#384](https://github.com/xgi-org/xgi/pull/384) (@nwlandry).
diff --git a/docs/source/conf.py b/docs/source/conf.py
index c7767d5..d37f8b9 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -21,7 +21,7 @@ sys.path.append(os.path.join(os.path.dirname(__name__), "xgi"))
 # -- Project information -----------------------------------------------------
 project = "XGI"
 copyright = "Copyright (C) 2021-2023 XGI Developers"
-release = "0.7"
+release = "0.7.1"
 
 # -- General configuration ---------------------------------------------------
 
diff --git a/setup.py b/setup.py
index 852f319..6fbaf18 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ import sys
 import setuptools
 from setuptools import setup
 
-__version__ = "0.7"
+__version__ = "0.7.1"
 
 if sys.version_info < (3, 8):
     sys.exit("XGI requires Python 3.8 or later.")
diff --git a/tutorials/Tutorial 6 - Statistics.ipynb b/tutorials/Tutorial 6 - Statistics.ipynb
index f941af9..448e28b 100644
--- a/tutorials/Tutorial 6 - Statistics.ipynb
+++ b/tutorials/Tutorial 6 - Statistics.ipynb
@@ -166,36 +166,6 @@
     "H.nodes([3, 4, 5]).degree.asdict()"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "id": "6c271d46",
-   "metadata": {},
-   "source": [
-    "The order of the degrees in the output respects the order of the nodes in the underlying\n",
-    "view."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 6,
-   "id": "01837d9c",
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "([3, 2, 2], [3, 2, 2])"
-      ]
-     },
-     "execution_count": 6,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "H.nodes([3, 4, 5]).degree.aslist(), H.nodes([4, 3, 5]).degree.aslist()"
-   ]
-  },
   {
    "cell_type": "markdown",
    "id": "eaf82dff",
diff --git a/xgi/classes/reportviews.py b/xgi/classes/reportviews.py
index eca433e..8f19ce4 100644
--- a/xgi/classes/reportviews.py
+++ b/xgi/classes/reportviews.py
@@ -111,18 +111,15 @@ class IDView(Mapping, Set):
         always use `x in view`.  The latter is always faster.
 
         """
-        return set(self._id_dict) if self._ids is None else self._ids
+        return set(self._ids)
 
     def __len__(self):
         """The number of IDs."""
-        return len(self._id_dict) if self._ids is None else len(self._ids)
+        return len(self._ids)
 
     def __iter__(self):
         """Returns an iterator over the IDs."""
-        if self._ids is None:
-            return iter({}) if self._id_dict is None else iter(self._id_dict)
-        else:
-            return iter(self._ids)
+        return iter(self._ids)
 
     def __getitem__(self, id):
         """Get the attributes of the ID.
diff --git a/xgi/convert.py b/xgi/convert.py
index b322338..9e36030 100644
--- a/xgi/convert.py
+++ b/xgi/convert.py
@@ -70,6 +70,10 @@ def convert_to_hypergraph(data, create_using=None):
     Hypergraph object
         A hypergraph constructed from the data
 
+    See Also
+    --------
+    from_max_simplices : Constructs a hypergraph from the maximal simplices of a simplicial complex.
+
     """
     if data is None:
         return empty_hypergraph(create_using)
@@ -92,7 +96,12 @@ def convert_to_hypergraph(data, create_using=None):
             return H
 
     elif isinstance(data, SimplicialComplex):
-        return from_max_simplices(data)
+        H = empty_hypergraph(create_using)
+        H.add_nodes_from((n, attr) for n, attr in data.nodes.items())
+        ee = data.edges
+        H.add_edges_from((ee.members(e), e, deepcopy(attr)) for e, attr in ee.items())
+        H._hypergraph = deepcopy(data._hypergraph)
+        return H
 
     elif isinstance(data, list):
         # edge list
@@ -735,8 +744,11 @@ def to_bipartite_graph(H, index=False):
     """
     G = nx.Graph()
 
-    node_dict = dict(zip(H.nodes, range(H.num_nodes)))
-    edge_dict = dict(zip(H.edges, range(H.num_nodes, H.num_nodes + H.num_edges)))
+    n = H.num_nodes
+    m = H.num_edges
+
+    node_dict = dict(zip(H.nodes, range(n)))
+    edge_dict = dict(zip(H.edges, range(n, n + m)))
     G.add_nodes_from(node_dict.values(), bipartite=0)
     G.add_nodes_from(edge_dict.values(), bipartite=1)
     for node in H.nodes:
@@ -746,8 +758,8 @@ def to_bipartite_graph(H, index=False):
     if index:
         return (
             G,
-            dict(zip(range(H.num_nodes), H.nodes)),
-            dict(zip(range(H.num_nodes, H.num_nodes + H.num_edges), H.edges)),
+            dict(zip(range(n), H.nodes)),
+            dict(zip(range(n, n + m), H.edges)),
         )
     else:
         return G
diff --git a/xgi/drawing/draw.py b/xgi/drawing/draw.py
index 97c96b6..023fda0 100644
--- a/xgi/drawing/draw.py
+++ b/xgi/drawing/draw.py
@@ -11,12 +11,13 @@ from matplotlib.colors import LinearSegmentedColormap, ListedColormap
 from matplotlib.patches import FancyArrow
 from mpl_toolkits.mplot3d.art3d import Line3DCollection, Poly3DCollection
 from networkx import spring_layout
+from numpy import ndarray
 from scipy.spatial import ConvexHull
 
 from .. import convert
 from ..classes import DiHypergraph, Hypergraph, SimplicialComplex, max_edge_order
 from ..exception import XGIError
-from ..stats import EdgeStat, NodeStat
+from ..stats import IDStat
 from .layout import _augmented_projection, barycenter_spring_layout
 
 __all__ = [
@@ -689,12 +690,12 @@ def draw_simplices(
     return ax
 
 
-def _scalar_arg_to_dict(arg, ids, min_val, max_val):
+def _scalar_arg_to_dict(scalar_arg, ids, min_val, max_val):
     """Map different types of arguments for drawing style to a dict with scalar values.
 
     Parameters
     ----------
-    arg : int, float, dict, iterable, or NodeStat/EdgeStat
+    scalar_arg : int, float, dict, iterable, or NodeStat/EdgeStat
         Attributes for drawing parameter.
     ids : NodeView or EdgeView
         This is the node or edge IDs that attributes get mapped to.
@@ -706,40 +707,89 @@ def _scalar_arg_to_dict(arg, ids, min_val, max_val):
     Returns
     -------
     dict
-        An ID: attribute dictionary.
+        An ID: scalar dictionary.
 
     Raises
     ------
     TypeError
         If a int, float, list, dict, or NodeStat/EdgeStat is not passed.
     """
-    if isinstance(arg, str):
+    if isinstance(scalar_arg, str):
         raise TypeError(
             "Argument must be int, float, dict, iterable, "
-            f"or NodeStat/EdgeStat. Received {type(arg)}"
+            f"or NodeStat/EdgeStat. Received {type(scalar_arg)}"
         )
-    elif isinstance(arg, dict):
-        return {id: arg[id] for id in arg if id in ids}
-    elif type(arg) in [int, float]:
-        return {id: arg for id in ids}
-    elif isinstance(arg, NodeStat) or isinstance(arg, EdgeStat):
-        vals = np.interp(arg.asnumpy(), [arg.min(), arg.max()], [min_val, max_val])
-        return dict(zip(ids, vals))
-    elif isinstance(arg, Iterable):
-        return {id: arg[idx] for idx, id in enumerate(ids)}
-    else:
-        raise TypeError(
-            "Argument must be int, float, dict, iterable, "
-            f"or NodeStat/EdgeStat. Received {type(arg)}"
+
+    # Single argument
+    if isinstance(scalar_arg, (int, float)):
+        return {id: scalar_arg for id in ids}
+
+    # IDStat
+    if isinstance(scalar_arg, IDStat):
+        vals = np.interp(
+            scalar_arg.asnumpy(),
+            [scalar_arg.min(), scalar_arg.max()],
+            [min_val, max_val],
         )
+        return dict(zip(ids, vals))
+
+    # Iterables of floats or ints
+    if isinstance(scalar_arg, Iterable):
+        if isinstance(scalar_arg, dict):
+            try:
+                return {id: float(scalar_arg[id]) for id in scalar_arg if id in ids}
+            except ValueError as e:
+                raise TypeError(
+                    "The input dict must have values that can be cast to floats."
+                )
 
+        elif isinstance(scalar_arg, (list, ndarray)):
+            try:
+                return {id: float(scalar_arg[idx]) for idx, id in enumerate(ids)}
+            except ValueError as e:
+                raise TypeError(
+                    "The input list or array must have values that can be cast to floats."
+                )
+        else:
+            raise TypeError(
+                "Argument must be an dict, list, or numpy array of floats or ints."
+            )
+
+    raise TypeError(
+        "Argument must be int, float, dict, iterable, "
+        f"or NodeStat/EdgeStat. Received {type(scalar_arg)}"
+    )
 
-def _color_arg_to_dict(arg, ids, cmap):
+
+def _color_arg_to_dict(color_arg, ids, cmap):
     """Map different types of arguments for drawing style to a dict with color values.
 
     Parameters
     ----------
-    arg : str, dict, iterable, or NodeStat/EdgeStat
+    color_arg : Several formats are accepted:
+
+        Single color values
+
+        * str
+        * 3- or 4-tuple
+
+        Iterable of colors (each color specified as above)
+
+        * numpy array
+        * list
+        * dict {id: color} pairs
+
+        Iterable of numerical values (floats or ints)
+
+        * list
+        * dict
+        * numpy array
+
+        Stats
+
+        * NodeStat
+        * EdgeStat
+
         Attributes for drawing parameter.
     ids : NodeView or EdgeView
         This is the node or edge IDs that attributes get mapped to.
@@ -749,33 +799,93 @@ def _color_arg_to_dict(arg, ids, cmap):
     Returns
     -------
     dict
-        An ID: attribute dictionary.
+        An ID: color dictionary.
 
     Raises
     ------
     TypeError
         If a string, dict, iterable, or NodeStat/EdgeStat is not passed.
+
+    Notes
+    -----
+    For the iterable of values, we do not accept tuples,
+    because there is the potential for ambiguity.
     """
-    if isinstance(arg, dict):
-        return {id: arg[id] for id in arg if id in ids}
-    elif isinstance(arg, str):
-        return {id: arg for id in ids}
-    elif isinstance(arg, (NodeStat, EdgeStat)):
+
+    # single argument. Must be a string or a tuple of floats
+    if isinstance(color_arg, str) or (
+        isinstance(color_arg, tuple) and isinstance(color_arg[0], float)
+    ):
+        return {id: color_arg for id in ids}
+
+    # Iterables of colors. The values of these iterables must strings or tuples. As of now,
+    # there is not a check to verify that the tuples contain floats.
+    if isinstance(color_arg, Iterable):
+        if isinstance(color_arg, dict) and isinstance(
+            next(iter(color_arg.values())), (str, tuple, ndarray)
+        ):
+            return {id: color_arg[id] for id in color_arg if id in ids}
+        if isinstance(color_arg, (list, ndarray)) and isinstance(
+            color_arg[0], (str, tuple, ndarray)
+        ):
+            return {id: color_arg[idx] for idx, id in enumerate(ids)}
+
+    # Stats or iterable of values
+    if isinstance(color_arg, (Iterable, IDStat)):
+        # set max and min of interpolation based on color map
         if isinstance(cmap, ListedColormap):
-            vals = np.interp(arg.asnumpy(), [arg.min(), arg.max()], [0, cmap.N])
+            minval = 0
+            maxval = cmap.N
         elif isinstance(cmap, LinearSegmentedColormap):
-            vals = np.interp(arg.asnumpy(), [arg.min(), arg.max()], [0.1, 0.9])
+            minval = 0.1
+            maxval = 0.9
         else:
             raise XGIError("Invalid colormap!")
 
-        return {id: np.array(cmap(vals[i])).reshape(1, -1) for i, id in enumerate(ids)}
-    elif isinstance(arg, Iterable):
-        return {id: arg[idx] for idx, id in enumerate(ids)}
-    else:
-        raise TypeError(
-            "Argument must be str, dict, iterable, or "
-            f"NodeStat/EdgeStat. Received {type(arg)}"
-        )
+        # handle the case of IDStat vs iterables
+        if isinstance(color_arg, IDStat):
+            vals = np.interp(
+                color_arg.asnumpy(),
+                [color_arg.min(), color_arg.max()],
+                [minval, maxval],
+            )
+            return {
+                id: np.array(cmap(vals[i])).reshape(1, -1) for i, id in enumerate(ids)
+            }
+
+        elif isinstance(color_arg, Iterable):
+            if isinstance(color_arg, dict) and isinstance(
+                next(iter(color_arg.values())), (int, float)
+            ):
+                v = list(color_arg.values())
+                vals = np.interp(v, [np.min(v), np.max(v)], [minval, maxval])
+                # because we have ids, we can't just assume that the keys of arg correspond to
+                # the ids.
+                return {
+                    id: np.array(cmap(v)).reshape(1, -1)
+                    for v, id in zip(vals, color_arg.keys())
+                    if id in ids
+                }
+
+            if isinstance(color_arg, (list, ndarray)) and isinstance(
+                color_arg[0], (int, float)
+            ):
+                vals = np.interp(
+                    color_arg, [np.min(color_arg), np.max(color_arg)], [minval, maxval]
+                )
+                return {
+                    id: np.array(cmap(vals[i])).reshape(1, -1)
+                    for i, id in enumerate(ids)
+                }
+            else:
+                raise TypeError(
+                    "Argument must be an dict, list, or numpy array of floats."
+                )
+
+    raise TypeError(
+        "Argument must be str, dict, iterable, or "
+        f"NodeStat/EdgeStat. Received {type(color_arg)}"
+    )
 
 
 def _CCW_sort(p):


Test patch:
diff --git a/tests/classes/test_hypergraph.py b/tests/classes/test_hypergraph.py
index f446e46..1a9dc26 100644
--- a/tests/classes/test_hypergraph.py
+++ b/tests/classes/test_hypergraph.py
@@ -40,6 +40,12 @@ def test_constructor(edgelist5, dict5, incidence5, dataframe5):
     with pytest.raises(XGIError):
         xgi.Hypergraph(1)
 
+    SC = xgi.SimplicialComplex(edgelist5)
+    H_sc = xgi.Hypergraph(SC)
+
+    assert set(H_sc.nodes) == set(SC.nodes)
+    assert H_sc.edges.members() == SC.edges.members()
+
 
 def test_hypergraph_attrs():
     H = xgi.Hypergraph()
diff --git a/tests/classes/test_reportviews.py b/tests/classes/test_reportviews.py
index c68d4e1..911be17 100644
--- a/tests/classes/test_reportviews.py
+++ b/tests/classes/test_reportviews.py
@@ -331,3 +331,16 @@ def test_maximal(edgelist5, edgelist8):
     # try strict=True
     m = H.edges.maximal(strict=True)
     assert set(m) == {5}
+
+
+def test_ids_type(edgelist5):
+    H = xgi.Hypergraph(edgelist5)
+    assert H.edges.ids == {0, 1, 2, 3}
+    assert H.edges([0, 1, 2, 3]).ids == {0, 1, 2, 3}
+    assert H.edges({0, 1, 2, 3}).ids == {0, 1, 2, 3}
+
+
+def test_ids_are_immutable(edgelist5):
+    H = xgi.Hypergraph(edgelist5)
+    H.edges.ids.add(42)
+    assert H.edges.ids == {0, 1, 2, 3}
diff --git a/tests/classes/test_simplicialcomplex.py b/tests/classes/test_simplicialcomplex.py
index 6f95c21..e861371 100644
--- a/tests/classes/test_simplicialcomplex.py
+++ b/tests/classes/test_simplicialcomplex.py
@@ -27,6 +27,13 @@ def test_constructor(edgelist5, dict5, incidence5, dataframe5):
     with pytest.raises(XGIError):
         xgi.SimplicialComplex(1)
 
+    H = xgi.Hypergraph(edgelist5)
+    S_h = xgi.SimplicialComplex(H)
+
+    assert set(S_h.nodes) == set(H.nodes) == set(S_list.nodes)
+    assert S_h.edges.members() == S_list.edges.members()
+    assert H.edges.members() <= S_h.edges.members()  # check it's a subset
+
 
 def test_string():
     S1 = xgi.SimplicialComplex()
diff --git a/tests/drawing/test_draw.py b/tests/drawing/test_draw.py
index 143ded3..aa8ec3a 100644
--- a/tests/drawing/test_draw.py
+++ b/tests/drawing/test_draw.py
@@ -57,29 +57,75 @@ def test_scalar_arg_to_dict(edgelist4):
         arg = "2"
         d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
 
+    with pytest.raises(TypeError):
+        arg = (1, 2, 3)
+        d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
+
 
 def test_color_arg_to_dict(edgelist4):
     ids = [1, 2, 3]
 
-    arg = "black"
-    d = _color_arg_to_dict(arg, ids, None)
+    # single values
+    arg1 = "black"
+    arg2 = (0.1, 0.2, 0.3)
+    arg3 = (0.1, 0.2, 0.3, 0.5)
+
+    # test iterables of colors
+    arg4 = [(0.1, 0.2, 0.3), (0.1, 0.2, 0.4), (0.1, 0.2, 0.5)]
+    arg5 = ["blue", "black", "red"]
+    arg6 = np.array(["blue", "black", "red"])
+    arg7 = {0: (0.1, 0.2, 0.3), 1: (0.1, 0.2, 0.4), 2: (0.1, 0.2, 0.5)}
+    arg8 = {0: "blue", 1: "black", 2: "red"}
+
+    # test iterables of values
+    arg9 = [0, 0.1, 0.2]
+    arg10 = {1: 0, 2: 0.1, 3: 0.2}
+    arg11 = np.array([0, 0.1, 0.2])
+
+    # test single values
+    d = _color_arg_to_dict(arg1, ids, None)
     assert d == {1: "black", 2: "black", 3: "black"}
 
-    with pytest.raises(TypeError):
-        arg = 0.3
-        d = _color_arg_to_dict(arg, ids, None)
+    d = _color_arg_to_dict(arg2, ids, None)
+    assert d == {1: (0.1, 0.2, 0.3), 2: (0.1, 0.2, 0.3), 3: (0.1, 0.2, 0.3)}
 
-    with pytest.raises(TypeError):
-        arg = 1
-        d = _color_arg_to_dict(arg, ids, None)
+    d = _color_arg_to_dict(arg3, ids, None)
+    for i in d:
+        assert np.allclose(d[i], np.array([0.1, 0.2, 0.3, 0.5]))
+
+    # Test iterables of colors
+    d = _color_arg_to_dict(arg4, ids, None)
+    assert d == {1: (0.1, 0.2, 0.3), 2: (0.1, 0.2, 0.4), 3: (0.1, 0.2, 0.5)}
+
+    d = _color_arg_to_dict(arg5, ids, None)
+    assert d == {1: "blue", 2: "black", 3: "red"}
+
+    d = _color_arg_to_dict(arg6, ids, None)
+    assert d == {1: "blue", 2: "black", 3: "red"}
 
-    arg = ["black", "blue", "red"]
-    d = _color_arg_to_dict(arg, ids, None)
-    assert d == {1: "black", 2: "blue", 3: "red"}
+    d = _color_arg_to_dict(arg7, ids, None)
+    assert d == {1: (0.1, 0.2, 0.4), 2: (0.1, 0.2, 0.5)}
 
-    arg = np.array(["black", "blue", "red"])
-    d = _color_arg_to_dict(arg, ids, None)
-    assert d == {1: "black", 2: "blue", 3: "red"}
+    d = _color_arg_to_dict(arg8, ids, None)
+    assert d == {1: "black", 2: "red"}
+
+    # Test iterables of values
+    cdict = {
+        1: np.array([[0.89173395, 0.93510188, 0.97539408, 1.0]]),
+        2: np.array([[0.41708574, 0.68063053, 0.83823145, 1.0]]),
+        3: np.array([[0.03137255, 0.28973472, 0.57031911, 1.0]]),
+    }
+    d = _color_arg_to_dict(arg9, ids, cm.Blues)
+    for i in d:
+        assert np.allclose(d[i], cdict[i])
+
+    d = _color_arg_to_dict(arg10, ids, cm.Blues)
+    for i in d:
+        assert np.allclose(d[i], cdict[i])
+
+    d = _color_arg_to_dict(arg11, ids, cm.Blues)
+    for i in d:
+        assert np.allclose(d[i], cdict[i])
 
     H = xgi.Hypergraph(edgelist4)
     arg = H.nodes.degree
@@ -88,6 +134,15 @@ def test_color_arg_to_dict(edgelist4):
     assert np.allclose(d[2], np.array([[0.98357555, 0.41279508, 0.28835063, 1.0]]))
     assert np.allclose(d[3], np.array([[0.59461745, 0.0461361, 0.07558631, 1.0]]))
 
+    # Test bad calls
+    with pytest.raises(TypeError):
+        arg = 0.3
+        d = _color_arg_to_dict(arg, ids, None)
+
+    with pytest.raises(TypeError):
+        arg = 1
+        d = _color_arg_to_dict(arg, ids, None)
+
 
 def test_draw(edgelist8):
     H = xgi.Hypergraph(edgelist8)
diff --git a/tests/linalg/test_matrix.py b/tests/linalg/test_matrix.py
index e9bca03..cd59ff2 100644
--- a/tests/linalg/test_matrix.py
+++ b/tests/linalg/test_matrix.py
@@ -486,23 +486,22 @@ def test_boundary_matrix(edgelist4):
     facedict1 = {k: v for v, k in facedict1.items()}
     tetdict1 = {k: v for v, k in tetdict1.items()}
 
-    iddict = S1.edges.ids
-    iddict = {simplex: v for v, simplex in iddict.items()}
-
-    i123 = facedict1[iddict[frozenset([1, 2, 3])]]
-    i2345 = tetdict1[iddict[frozenset([2, 3, 4, 5])]]
-    i345 = facedict1[iddict[frozenset([3, 4, 5])]]
-    i245 = facedict1[iddict[frozenset([2, 4, 5])]]
-    i12 = edgedict1[iddict[frozenset([1, 2])]]
-    i24 = edgedict1[iddict[frozenset([2, 4])]]
-    i34 = edgedict1[iddict[frozenset([3, 4])]]
-    i235 = facedict1[iddict[frozenset([2, 3, 5])]]
-    i23 = edgedict1[iddict[frozenset([2, 3])]]
-    i45 = edgedict1[iddict[frozenset([4, 5])]]
-    i234 = facedict1[iddict[frozenset([2, 3, 4])]]
-    i25 = edgedict1[iddict[frozenset([2, 5])]]
-    i13 = edgedict1[iddict[frozenset([1, 3])]]
-    i35 = edgedict1[iddict[frozenset([3, 5])]]
+    members_to_id = {simplex: v for v, simplex in S1.edges.members(dtype=dict).items()}
+
+    i123 = facedict1[members_to_id[frozenset([1, 2, 3])]]
+    i2345 = tetdict1[members_to_id[frozenset([2, 3, 4, 5])]]
+    i345 = facedict1[members_to_id[frozenset([3, 4, 5])]]
+    i245 = facedict1[members_to_id[frozenset([2, 4, 5])]]
+    i12 = edgedict1[members_to_id[frozenset([1, 2])]]
+    i24 = edgedict1[members_to_id[frozenset([2, 4])]]
+    i34 = edgedict1[members_to_id[frozenset([3, 4])]]
+    i235 = facedict1[members_to_id[frozenset([2, 3, 5])]]
+    i23 = edgedict1[members_to_id[frozenset([2, 3])]]
+    i45 = edgedict1[members_to_id[frozenset([4, 5])]]
+    i234 = facedict1[members_to_id[frozenset([2, 3, 4])]]
+    i25 = edgedict1[members_to_id[frozenset([2, 5])]]
+    i13 = edgedict1[members_to_id[frozenset([1, 3])]]
+    i35 = edgedict1[members_to_id[frozenset([3, 5])]]
 
     assert B1[nodedict1[1], i12] == -1
     assert B1[nodedict1[2], i12] == 1
@@ -546,7 +545,7 @@ def test_boundary_matrix(edgelist4):
     assert np.linalg.norm(B2 @ B3) == 0
 
     # Change the orientation of a face
-    orientations[iddict[frozenset([3, 4, 5])]] = 1
+    orientations[members_to_id[frozenset([3, 4, 5])]] = 1
 
     B1 = xgi.boundary_matrix(S1, order=1, orientations=orientations, index=False)
     B2 = xgi.boundary_matrix(S1, order=2, orientations=orientations, index=False)
diff --git a/tests/stats/test_nodestats.py b/tests/stats/test_nodestats.py
index 27fd1f5..6506a33 100644
--- a/tests/stats/test_nodestats.py
+++ b/tests/stats/test_nodestats.py
@@ -518,7 +518,7 @@ def test_multi_stats_aspandas(edgelist1, edgelist8):
         ["average_neighbor_degree", "degree", H.nodes.degree(order=2)]
     )
     df = pd.DataFrame(multi.asdict(transpose=True))
-    pd.testing.assert_frame_equal(df, multi.aspandas())
+    pd.testing.assert_frame_equal(df, multi.aspandas(), check_like=True)
 
     H = xgi.Hypergraph(edgelist8)
     multi = H.nodes.multi(
diff --git a/tests/test_convert.py b/tests/test_convert.py
index 38b787c..f733ee0 100644
--- a/tests/test_convert.py
+++ b/tests/test_convert.py
@@ -24,7 +24,7 @@ def test_convert_simplicial_complex_to_hypergraph():
     H = xgi.convert_to_hypergraph(SC)
     assert isinstance(H, xgi.Hypergraph)
     assert SC.nodes == H.nodes
-    assert SC.edges.maximal().members() == H.edges.members()
+    assert SC.edges.members() == H.edges.members()
 
 
 def test_convert_list_to_hypergraph(edgelist2):