Repository: plotly/plotly.py

Issue:
Coloring by boolean column type does not work
When trying to color a scatter plot by a `bool` column datatype, the colors do not show:

(see attached file for data)

```
grouped.dtypes

mean_ape            float64
measurement_type     object
note                 object
parameters           object
n_estimators          int64
max_depth             int64
bootstrap              bool
max_features        float64

fig = px.scatter(
    grouped,
    x="n_estimators",
    y="mean_ape",
    facet_row="measurement_type",
    color="bootstrap",
    title="Error vs Number of Estimators Colored by Bootstrap",
    height=1000, width=1600
)
```
<img width="1433" alt="Screen Shot 2019-06-17 at 08 52 13 EDT" src="https://user-images.githubusercontent.com/17484616/59605663-45849600-90dd-11e9-898b-155736577bb6.png">

However, if I convert the `bool` column to a `str`, then the colors correctly appear:

```
new_grouped = grouped.copy()
new_grouped['bootstrap'] = new_grouped['bootstrap'].astype(str)

fig = px.scatter(
    new_grouped,
    x="n_estimators",
    y="mean_ape",
    facet_row="measurement_type",
    color="bootstrap",
    title="Error vs Number of Estimators Colored by Bootstrap",
    height=1000, width=1600
)
```

<img width="1441" alt="Screen Shot 2019-06-17 at 08 53 13 EDT" src="https://user-images.githubusercontent.com/17484616/59605745-6e0c9000-90dd-11e9-95d4-9ecd24e20b24.png">

[grouped.csv.zip](https://github.com/plotly/plotly_express/files/3297037/grouped.csv.zip)




Discussion:
Definitely a bug, we'll fix it! thanks for the detailed report :)

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
@@ -188,7 +188,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
                 if ((not attr_value) or (name in attr_value))
                 and (
                     trace_spec.constructor != go.Parcoords
-                    or args["data_frame"][name].dtype.kind in "bifc"
+                    or args["data_frame"][name].dtype.kind in "ifc"
                 )
                 and (
                     trace_spec.constructor != go.Parcats
@@ -1123,7 +1123,7 @@ def aggfunc_discrete(x):
     agg_f[count_colname] = "sum"
 
     if args["color"]:
-        if df[args["color"]].dtype.kind not in "bifc":
+        if df[args["color"]].dtype.kind not in "ifc":
             aggfunc_color = aggfunc_discrete
             discrete_color = True
         elif not aggfunc_color:
@@ -1167,8 +1167,13 @@ def aggfunc_continuous(x):
             df_tree[cols] = dfg[cols]
         df_all_trees = df_all_trees.append(df_tree, ignore_index=True)
 
+    # we want to make sure than (?) is the first color of the sequence
     if args["color"] and discrete_color:
-        df_all_trees = df_all_trees.sort_values(by=args["color"])
+        sort_col_name = "sort_color_if_discrete_color"
+        while sort_col_name in df_all_trees.columns:
+            sort_col_name += "0"
+        df_all_trees[sort_col_name] = df[args["color"]].astype(str)
+        df_all_trees = df_all_trees.sort_values(by=sort_col_name)
 
     # Now modify arguments
     args["data_frame"] = df_all_trees
@@ -1222,7 +1227,7 @@ def infer_config(args, constructor, trace_patch):
             else:
                 if (
                     args["color"]
-                    and args["data_frame"][args["color"]].dtype.kind in "bifc"
+                    and args["data_frame"][args["color"]].dtype.kind in "ifc"
                 ):
                     attrs.append("color")
                     args["color_is_continuous"] = True


Test patch:
diff --git a/test/percy/plotly-express.py b/test/percy/plotly-express.py
--- a/test/percy/plotly-express.py
+++ b/test/percy/plotly-express.py
@@ -550,3 +550,39 @@
     y=["first", "second", "first", "second"], x=[3, 1, 4, 2], color=["A", "A", "B", "B"]
 )
 fig.write_html(os.path.join(dir_name, "funnel.html"), auto_play=False)
+
+import plotly.express as px
+
+fig = px.scatter(x=[1, 2, 1, 2], y=[4, 3, 2, 4], color=[True, True, False, False])
+fig.write_html(os.path.join(dir_name, "scatter_bool_color.html"), auto_play=False)
+
+
+import plotly.express as px
+
+fig = px.pie(values=[1, 2, 3, 4], color=[True, False, True, False])
+fig.write_html(os.path.join(dir_name, "pie_bool_color.html"), auto_play=False)
+
+import plotly.express as px
+import numpy as np
+
+df = px.data.gapminder().query("year == 2007")
+np.random.seed(0)
+df["color"] = np.random.choice([True, False], len(df))
+fig = px.choropleth(df, locations="iso_alpha", color="color")
+fig.write_html(os.path.join(dir_name, "choropleth_bool_color.html"), auto_play=False)
+
+import plotly.express as px
+
+df = px.data.iris()
+df["is_setosa"] = df["species"] == "setosa"
+fig = px.density_contour(df, x="sepal_width", y="sepal_length", color="is_setosa")
+fig.write_html(
+    os.path.join(dir_name, "density_contour_bool_color.html"), auto_play=False
+)
+
+import plotly.express as px
+
+fig = px.sunburst(
+    path=[["yes", "no", "no"], ["yes", "no", "a"]], color=[True, False, True]
+)
+fig.write_html(os.path.join(dir_name, "sunburst_bool_color.html"), auto_play=False)