Repository: networkx/networkx

Issue:
Rename nx.join to something which gives more context about operations on trees
While reviewing #6503 [@dschult noted](https://github.com/networkx/networkx/pull/6503#issuecomment-1707200999) that the current function name `nx.join` doesn't give enough information to the user. The function is used to "join" trees.

This will require a deprecation cycle.


Discussion:

Resolving patch:
diff --git a/networkx/algorithms/tree/operations.py b/networkx/algorithms/tree/operations.py
--- a/networkx/algorithms/tree/operations.py
+++ b/networkx/algorithms/tree/operations.py
@@ -4,13 +4,42 @@
 
 import networkx as nx
 
-__all__ = ["join"]
+__all__ = ["join", "join_trees"]
 
 
 def join(rooted_trees, label_attribute=None):
-    """Returns a new rooted tree with a root node joined with the roots
+    """A deprecated name for `join_trees`
+
+    Returns a new rooted tree with a root node joined with the roots
     of each of the given rooted trees.
 
+    .. deprecated:: 3.2
+
+       `join` is deprecated in NetworkX v3.2 and will be removed in v3.4.
+       It has been renamed join_trees with the same syntax/interface.
+
+    """
+    import warnings
+
+    warnings.warn(
+        "The function `join` is deprecated and is renamed `join_trees`.\n"
+        "The ``join`` function itself will be removed in v3.4",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+
+    return join_trees(rooted_trees, label_attribute=label_attribute)
+
+
+def join_trees(rooted_trees, label_attribute=None):
+    """Returns a new rooted tree made by joining `rooted_trees`
+
+    Constructs a new tree by joining each tree in `rooted_trees`.
+    A new root node is added and connected to each of the roots
+    of the input trees. While copying the nodes from the trees,
+    relabeling to integers occurs and the old name stored as an
+    attribute of the new node in the returned graph.
+
     Parameters
     ----------
     rooted_trees : list
@@ -35,6 +64,10 @@ def join(rooted_trees, label_attribute=None):
 
     Notes
     -----
+    Trees are stored in NetworkX as NetworkX Graphs. There is no specific
+    enforcement of the fact that these are trees. Testing for each tree
+    can be done using :func:`networkx.is_tree`.
+
     Graph, edge, and node attributes are propagated from the given
     rooted trees to the created tree. If there are any overlapping graph
     attributes, those from later trees will overwrite those from earlier


Test patch:
diff --git a/networkx/algorithms/tree/tests/test_operations.py b/networkx/algorithms/tree/tests/test_operations.py
--- a/networkx/algorithms/tree/tests/test_operations.py
+++ b/networkx/algorithms/tree/tests/test_operations.py
@@ -1,7 +1,3 @@
-"""Unit tests for the :mod:`networkx.algorithms.tree.operations` module.
-
-"""
-
 from itertools import chain
 
 import networkx as nx
@@ -16,36 +12,29 @@ def _check_label_attribute(input_trees, res_tree, label_attribute="_old"):
     return res_attr_set == input_label_set
 
 
-class TestJoin:
-    """Unit tests for the :func:`networkx.tree.join` function."""
-
-    def test_empty_sequence(self):
-        """Tests that joining the empty sequence results in the tree
-        with one node.
-
-        """
-        T = nx.join([])
-        assert len(T) == 1
-        assert T.number_of_edges() == 0
-
-    def test_single(self):
-        """Tests that joining just one tree yields a tree with one more
-        node.
-
-        """
-        T = nx.empty_graph(1)
-        trees = [(T, 0)]
-        actual = nx.join(trees)
-        expected = nx.path_graph(2)
-        assert nodes_equal(list(expected), list(actual))
-        assert edges_equal(list(expected.edges()), list(actual.edges()))
-        assert _check_label_attribute(trees, actual)
-
-    def test_basic(self):
-        """Tests for joining multiple subtrees at a root node."""
-        trees = [(nx.full_rary_tree(2, 2**2 - 1), 0) for i in range(2)]
-        label_attribute = "old_values"
-        actual = nx.join(trees, label_attribute)
-        expected = nx.full_rary_tree(2, 2**3 - 1)
-        assert nx.is_isomorphic(actual, expected)
-        assert _check_label_attribute(trees, actual, label_attribute)
+def test_empty_sequence():
+    """Joining the empty sequence results in the tree with one node."""
+    T = nx.join_trees([])
+    assert len(T) == 1
+    assert T.number_of_edges() == 0
+
+
+def test_single():
+    """Joining just one tree yields a tree with one more node."""
+    T = nx.empty_graph(1)
+    trees = [(T, 0)]
+    actual = nx.join_trees(trees)
+    expected = nx.path_graph(2)
+    assert nodes_equal(list(expected), list(actual))
+    assert edges_equal(list(expected.edges()), list(actual.edges()))
+    assert _check_label_attribute(trees, actual)
+
+
+def test_basic():
+    """Joining multiple subtrees at a root node."""
+    trees = [(nx.full_rary_tree(2, 2**2 - 1), 0) for i in range(2)]
+    label_attribute = "old_values"
+    actual = nx.join_trees(trees, label_attribute)
+    expected = nx.full_rary_tree(2, 2**3 - 1)
+    assert nx.is_isomorphic(actual, expected)
+    assert _check_label_attribute(trees, actual, label_attribute)