Repository: plotly/plotly.py

Issue:
cannot use column name "parent" for the dataframe passed to sunburst graph
Passing a dataframe having the name of a column being "parent" causes a `ValueError`

# Error

```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~\Miniconda3\lib\site-packages\pandas\core\frame.py in _ensure_valid_index(self, value)
   3423             try:
-> 3424                 value = Series(value)
   3425             except (ValueError, NotImplementedError, TypeError):

~\Miniconda3\lib\site-packages\pandas\core\series.py in __init__(self, data, index, dtype, name, copy, fastpath)
    263 
--> 264                 data = SingleBlockManager(data, index, fastpath=True)
    265 

~\Miniconda3\lib\site-packages\pandas\core\internals\managers.py in __init__(self, block, axis, do_integrity_check, fastpath)
   1480         if not isinstance(block, Block):
-> 1481             block = make_block(block, placement=slice(0, len(axis)), ndim=1)
   1482 

~\Miniconda3\lib\site-packages\pandas\core\internals\blocks.py in make_block(values, placement, klass, ndim, dtype, fastpath)
   3094 
-> 3095     return klass(values, ndim=ndim, placement=placement)
   3096 

~\Miniconda3\lib\site-packages\pandas\core\internals\blocks.py in __init__(self, values, placement, ndim)
   2630         super(ObjectBlock, self).__init__(values, ndim=ndim,
-> 2631                                           placement=placement)
   2632 

~\Miniconda3\lib\site-packages\pandas\core\internals\blocks.py in __init__(self, values, placement, ndim)
     86                 'Wrong number of items passed {val}, placement implies '
---> 87                 '{mgr}'.format(val=len(self.values), mgr=len(self.mgr_locs)))
     88 

ValueError: Wrong number of items passed 2, placement implies 0

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
<ipython-input-56-4e466ed13d60> in <module>
      9 )
     10 print(df)
---> 11 fig = px.sunburst(df, path=['regions', 'sectors', 'parent'], values='sales')
     12 fig.show()

~\Miniconda3\lib\site-packages\plotly\express\_chart_types.py in sunburst(data_frame, names, values, parents, path, ids, color, color_continuous_scale, range_color, color_continuous_midpoint, color_discrete_sequence, color_discrete_map, hover_name, hover_data, custom_data, labels, title, template, width, height, branchvalues, maxdepth)
   1334         constructor=go.Sunburst,
   1335         trace_patch=dict(branchvalues=branchvalues, maxdepth=maxdepth),
-> 1336         layout_patch=layout_patch,
   1337     )
   1338 

~\Miniconda3\lib\site-packages\plotly\express\_core.py in make_figure(args, constructor, trace_patch, layout_patch)
   1795     args = build_dataframe(args, constructor)
   1796     if constructor in [go.Treemap, go.Sunburst] and args["path"] is not None:
-> 1797         args = process_dataframe_hierarchy(args)
   1798 
   1799     trace_specs, grouped_mappings, sizeref, show_colorbar = infer_config(

~\Miniconda3\lib\site-packages\plotly\express\_core.py in process_dataframe_hierarchy(args)
   1546     #  Set column type here (useful for continuous vs discrete colorscale)
   1547     for col in cols:
-> 1548         df_all_trees[col] = df_all_trees[col].astype(df[col].dtype)
   1549     for i, level in enumerate(path):
   1550         df_tree = pd.DataFrame(columns=df_all_trees.columns)

~\Miniconda3\lib\site-packages\pandas\core\frame.py in __setitem__(self, key, value)
   3368         else:
   3369             # set column
-> 3370             self._set_item(key, value)
   3371 
   3372     def _setitem_slice(self, key, value):

~\Miniconda3\lib\site-packages\pandas\core\frame.py in _set_item(self, key, value)
   3442         """
   3443 
-> 3444         self._ensure_valid_index(value)
   3445         value = self._sanitize_column(key, value)
   3446         NDFrame._set_item(self, key, value)

~\Miniconda3\lib\site-packages\pandas\core\frame.py in _ensure_valid_index(self, value)
   3424                 value = Series(value)
   3425             except (ValueError, NotImplementedError, TypeError):
-> 3426                 raise ValueError('Cannot set a frame with no defined index '
   3427                                  'and a value that cannot be converted to a '
   3428                                  'Series')

ValueError: Cannot set a frame with no defined index and a value that cannot be converted to a Series
```

# Example

Using the sunburst example in the documentation
```python
vendors = ["A", "B", "C", "D", None, "E", "F", "G", "H", None]
sectors = ["Tech", "Tech", "Finance", "Finance", "Other",
           "Tech", "Tech", "Finance", "Finance", "Other"]
regions = ["North", "North", "North", "North", "North",
           "South", "South", "South", "South", "South"]
sales = [1, 3, 2, 4, 1, 2, 2, 1, 4, 1]
df = pd.DataFrame(
    dict(parent=vendors, sectors=sectors, regions=regions, sales=sales) #here I simply changed the name "vendor" to "parent"
)
print(df)
fig = px.sunburst(df, path=['regions', 'sectors', 'parent'], values='sales')
fig.show()
```

# Environment
pandas   '0.24.2'
plotly     '4.8.2'


Discussion:

Resolving patch:
diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py
--- a/packages/python/plotly/plotly/express/_core.py
+++ b/packages/python/plotly/plotly/express/_core.py
@@ -1546,8 +1546,9 @@ def aggfunc_continuous(x):
     for col in cols:  # for hover_data, custom_data etc.
         if col not in agg_f:
             agg_f[col] = aggfunc_discrete
+    # Avoid collisions with reserved names - columns in the path have been copied already
+    cols = list(set(cols) - set(["labels", "parent", "id"]))
     # ----------------------------------------------------------------------------
-
     df_all_trees = pd.DataFrame(columns=["labels", "parent", "id"] + cols)
     #  Set column type here (useful for continuous vs discrete colorscale)
     for col in cols:


Test patch:
diff --git a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py
--- a/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py
+++ b/packages/python/plotly/plotly/tests/test_core/test_px/test_px_functions.py
@@ -250,6 +250,26 @@ def test_sunburst_treemap_with_path_color():
     assert np.all(np.array(colors[:8]) == np.array(calls))
 
 
+def test_sunburst_treemap_column_parent():
+    vendors = ["A", "B", "C", "D", "E", "F", "G", "H"]
+    sectors = [
+        "Tech",
+        "Tech",
+        "Finance",
+        "Finance",
+        "Tech",
+        "Tech",
+        "Finance",
+        "Finance",
+    ]
+    regions = ["North", "North", "North", "North", "South", "South", "South", "South"]
+    values = [1, 3, 2, 4, 2, 2, 1, 4]
+    df = pd.DataFrame(dict(id=vendors, sectors=sectors, parent=regions, values=values,))
+    path = ["parent", "sectors", "id"]
+    # One column of the path is a reserved name - this is ok and should not raise
+    fig = px.sunburst(df, path=path, values="values")
+
+
 def test_sunburst_treemap_with_path_non_rectangular():
     vendors = ["A", "B", "C", "D", None, "E", "F", "G", "H", None]
     sectors = [