From 9941749e55b868536ae30ccd1cab8b1e2a86dee7 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Wed, 8 Jun 2022 09:35:09 -0400 Subject: [PATCH 01/11] short circuit more machinery when one_group --- .../python/plotly/plotly/express/_core.py | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index f4aa02cdc21..1a33ff218d4 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1898,6 +1898,7 @@ def infer_config(args, constructor, trace_patch, layout_patch): # Create grouped mappings grouped_mappings = [make_mapping(args, a) for a in grouped_attrs] + grouped_mappings = [x for x in grouped_mappings if x.grouper] # Create trace specs trace_specs = make_trace_spec(args, constructor, attrs, trace_patch) @@ -1915,15 +1916,18 @@ def get_orderings(args, grouper, grouped): of tuples like [("value1", ""), ("value2", "")] where each tuple contains the name of a single dimension-group """ - orders = {} if "category_orders" not in args else args["category_orders"].copy() + + if grouper == [one_group]: + sorted_group_names = [("",)] + return orders, sorted_group_names + for col in grouper: - if col != one_group: - uniques = list(args["data_frame"][col].unique()) - if col not in orders: - orders[col] = uniques - else: - orders[col] = list(OrderedDict.fromkeys(list(orders[col]) + uniques)) + uniques = list(args["data_frame"][col].unique()) + if col not in orders: + orders[col] = uniques + else: + orders[col] = list(OrderedDict.fromkeys(list(orders[col]) + uniques)) sorted_group_names = [] for group_name in grouped.groups: @@ -1932,11 +1936,10 @@ def get_orderings(args, grouper, grouped): sorted_group_names.append(group_name) for i, col in reversed(list(enumerate(grouper))): - if col != one_group: - sorted_group_names = sorted( - sorted_group_names, - key=lambda g: orders[col].index(g[i]) if g[i] in orders[col] else -1, - ) + sorted_group_names = sorted( + sorted_group_names, + key=lambda g: orders[col].index(g[i]) if g[i] in orders[col] else -1, + ) return orders, sorted_group_names @@ -1955,8 +1958,12 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None): trace_specs, grouped_mappings, sizeref, show_colorbar = infer_config( args, constructor, trace_patch, layout_patch ) - grouper = [x.grouper or one_group for x in grouped_mappings] or [one_group] - grouped = args["data_frame"].groupby(grouper, sort=False) + if len(grouped_mappings): + grouper = [x.grouper for x in grouped_mappings] + grouped = args["data_frame"].groupby(grouper, sort=False) + else: + grouper = [one_group] + grouped = None orders, sorted_group_names = get_orderings(args, grouper, grouped) @@ -1988,7 +1995,12 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None): trace_name_labels = None facet_col_wrap = args.get("facet_col_wrap", 0) for group_name in sorted_group_names: - group = grouped.get_group(group_name if len(group_name) > 1 else group_name[0]) + if grouped is not None: + group = grouped.get_group( + group_name if len(group_name) > 1 else group_name[0] + ) + else: + group = args["data_frame"] mapping_labels = OrderedDict() trace_name_labels = OrderedDict() frame_name = "" From b97d1978d4b336dbbbfdaac683e5cea76c3c94ba Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Wed, 8 Jun 2022 21:36:56 -0400 Subject: [PATCH 02/11] more correct optimization --- .../python/plotly/plotly/express/_core.py | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 1a33ff218d4..5dce0b75391 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1898,7 +1898,6 @@ def infer_config(args, constructor, trace_patch, layout_patch): # Create grouped mappings grouped_mappings = [make_mapping(args, a) for a in grouped_attrs] - grouped_mappings = [x for x in grouped_mappings if x.grouper] # Create trace specs trace_specs = make_trace_spec(args, constructor, attrs, trace_patch) @@ -1918,16 +1917,17 @@ def get_orderings(args, grouper, grouped): """ orders = {} if "category_orders" not in args else args["category_orders"].copy() - if grouper == [one_group]: - sorted_group_names = [("",)] + if _all_one_group(grouper): + sorted_group_names = [("",) * len(grouper)] return orders, sorted_group_names for col in grouper: - uniques = list(args["data_frame"][col].unique()) - if col not in orders: - orders[col] = uniques - else: - orders[col] = list(OrderedDict.fromkeys(list(orders[col]) + uniques)) + if col != one_group: + uniques = list(args["data_frame"][col].unique()) + if col not in orders: + orders[col] = uniques + else: + orders[col] = list(OrderedDict.fromkeys(list(orders[col]) + uniques)) sorted_group_names = [] for group_name in grouped.groups: @@ -1936,13 +1936,21 @@ def get_orderings(args, grouper, grouped): sorted_group_names.append(group_name) for i, col in reversed(list(enumerate(grouper))): - sorted_group_names = sorted( - sorted_group_names, - key=lambda g: orders[col].index(g[i]) if g[i] in orders[col] else -1, - ) + if col != one_group: + sorted_group_names = sorted( + sorted_group_names, + key=lambda g: orders[col].index(g[i]) if g[i] in orders[col] else -1, + ) return orders, sorted_group_names +def _all_one_group(grouper): + for g in grouper: + if g != one_group: + return False + return True + + def make_figure(args, constructor, trace_patch=None, layout_patch=None): trace_patch = trace_patch or {} layout_patch = layout_patch or {} @@ -1958,12 +1966,10 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None): trace_specs, grouped_mappings, sizeref, show_colorbar = infer_config( args, constructor, trace_patch, layout_patch ) - if len(grouped_mappings): - grouper = [x.grouper for x in grouped_mappings] + grouper = [x.grouper or one_group for x in grouped_mappings] or [one_group] + grouped = None + if not _all_one_group(grouper): grouped = args["data_frame"].groupby(grouper, sort=False) - else: - grouper = [one_group] - grouped = None orders, sorted_group_names = get_orderings(args, grouper, grouped) From 6640d8f426299e15f2ba17bd9a25efd469550cbc Mon Sep 17 00:00:00 2001 From: jvdd Date: Thu, 9 Jun 2022 10:47:33 +0200 Subject: [PATCH 03/11] :recycle: check for all same groups --- packages/python/plotly/plotly/express/_core.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 5dce0b75391..03b053567ec 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1904,7 +1904,7 @@ def infer_config(args, constructor, trace_patch, layout_patch): return trace_specs, grouped_mappings, sizeref, show_colorbar -def get_orderings(args, grouper, grouped): +def get_orderings(args, grouper, grouped, all_same_group): """ `orders` is the user-supplied ordering with the remaining data-frame-supplied ordering appended if the column is used for grouping. It includes anything the user @@ -1917,7 +1917,7 @@ def get_orderings(args, grouper, grouped): """ orders = {} if "category_orders" not in args else args["category_orders"].copy() - if _all_one_group(grouper): + if all_same_group: sorted_group_names = [("",) * len(grouper)] return orders, sorted_group_names @@ -1944,10 +1944,12 @@ def get_orderings(args, grouper, grouped): return orders, sorted_group_names -def _all_one_group(grouper): - for g in grouper: +def _all_same_group(args, grouper): + for g in set(grouper): if g != one_group: - return False + arr = args["data_frame"][g].values + if not (arr[0] == arr).all(axis=0): + return False return True @@ -1968,10 +1970,11 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None): ) grouper = [x.grouper or one_group for x in grouped_mappings] or [one_group] grouped = None - if not _all_one_group(grouper): + all_same_group = _all_same_group(args, grouper) + if not all_same_group: grouped = args["data_frame"].groupby(grouper, sort=False) - orders, sorted_group_names = get_orderings(args, grouper, grouped) + orders, sorted_group_names = get_orderings(args, grouper, grouped, all_same_group) col_labels = [] row_labels = [] From 73b3c7c25d0d07499900035f94db4412a31be088 Mon Sep 17 00:00:00 2001 From: jvdd Date: Thu, 9 Jun 2022 16:40:24 +0200 Subject: [PATCH 04/11] :bug: make orders and sorted_group_names backwards compatible --- packages/python/plotly/plotly/express/_core.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 03b053567ec..8638c00fc47 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1916,10 +1916,17 @@ def get_orderings(args, grouper, grouped, all_same_group): of a single dimension-group """ orders = {} if "category_orders" not in args else args["category_orders"].copy() + sorted_group_names = [] if all_same_group: - sorted_group_names = [("",) * len(grouper)] - return orders, sorted_group_names + for col in grouper: + if col != one_group: + single_val = args["data_frame"][col].iloc[0] + sorted_group_names.append(single_val) + orders[col] = [single_val] + else: + sorted_group_names.append("") + return orders, [tuple(sorted_group_names)] for col in grouper: if col != one_group: @@ -1929,7 +1936,6 @@ def get_orderings(args, grouper, grouped, all_same_group): else: orders[col] = list(OrderedDict.fromkeys(list(orders[col]) + uniques)) - sorted_group_names = [] for group_name in grouped.groups: if len(grouper) == 1: group_name = (group_name,) From 3ae064520e099ce2e591c3f388cc209386311f30 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Thu, 9 Jun 2022 11:06:30 -0400 Subject: [PATCH 05/11] reorder computation to avoid an extra scan through the data --- .../python/plotly/plotly/express/_core.py | 68 ++++++++----------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 8638c00fc47..d18a321a80b 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1904,7 +1904,7 @@ def infer_config(args, constructor, trace_patch, layout_patch): return trace_specs, grouped_mappings, sizeref, show_colorbar -def get_orderings(args, grouper, grouped, all_same_group): +def get_orderings(args, grouper): """ `orders` is the user-supplied ordering with the remaining data-frame-supplied ordering appended if the column is used for grouping. It includes anything the user @@ -1916,47 +1916,42 @@ def get_orderings(args, grouper, grouped, all_same_group): of a single dimension-group """ orders = {} if "category_orders" not in args else args["category_orders"].copy() - sorted_group_names = [] - - if all_same_group: - for col in grouper: - if col != one_group: - single_val = args["data_frame"][col].iloc[0] - sorted_group_names.append(single_val) - orders[col] = [single_val] - else: - sorted_group_names.append("") - return orders, [tuple(sorted_group_names)] + # figure out orders and what the single group name would be if there were one + single_group_name = [] for col in grouper: - if col != one_group: + if col == one_group: + single_group_name.append("") + else: uniques = list(args["data_frame"][col].unique()) + if len(uniques) == 1: + single_group_name.append(uniques[0]) if col not in orders: orders[col] = uniques else: orders[col] = list(OrderedDict.fromkeys(list(orders[col]) + uniques)) - for group_name in grouped.groups: - if len(grouper) == 1: - group_name = (group_name,) - sorted_group_names.append(group_name) - - for i, col in reversed(list(enumerate(grouper))): - if col != one_group: - sorted_group_names = sorted( - sorted_group_names, - key=lambda g: orders[col].index(g[i]) if g[i] in orders[col] else -1, - ) - return orders, sorted_group_names - + if len(single_group_name) == len(grouper): + # we have a single group, so we can skip all group-by operations! + grouped = None + sorted_group_names = [tuple(single_group_name)] + else: + grouped = args["data_frame"].groupby(grouper, sort=False) + sorted_group_names = [] + for group_name in grouped.groups: + if len(grouper) == 1: + group_name = (group_name,) + sorted_group_names.append(group_name) -def _all_same_group(args, grouper): - for g in set(grouper): - if g != one_group: - arr = args["data_frame"][g].values - if not (arr[0] == arr).all(axis=0): - return False - return True + for i, col in reversed(list(enumerate(grouper))): + if col != one_group: + sorted_group_names = sorted( + sorted_group_names, + key=lambda g: orders[col].index(g[i]) + if g[i] in orders[col] + else -1, + ) + return grouped, orders, sorted_group_names def make_figure(args, constructor, trace_patch=None, layout_patch=None): @@ -1975,12 +1970,7 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None): args, constructor, trace_patch, layout_patch ) grouper = [x.grouper or one_group for x in grouped_mappings] or [one_group] - grouped = None - all_same_group = _all_same_group(args, grouper) - if not all_same_group: - grouped = args["data_frame"].groupby(grouper, sort=False) - - orders, sorted_group_names = get_orderings(args, grouper, grouped, all_same_group) + grouped, orders, sorted_group_names = get_orderings(args, grouper) col_labels = [] row_labels = [] From 067d4b01f4a6d67e1ee4d86266e9b359df5526a6 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Thu, 9 Jun 2022 12:52:30 -0400 Subject: [PATCH 06/11] optimize group access --- .../python/plotly/plotly/express/_core.py | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index d18a321a80b..34412eda744 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1904,44 +1904,42 @@ def infer_config(args, constructor, trace_patch, layout_patch): return trace_specs, grouped_mappings, sizeref, show_colorbar -def get_orderings(args, grouper): +def get_groups_and_orders(args, grouper): """ `orders` is the user-supplied ordering with the remaining data-frame-supplied ordering appended if the column is used for grouping. It includes anything the user gave, for any variable, including values not present in the dataset. It's a dict where the keys are e.g. "x" or "color" - `sorted_group_names` is the set of groups, ordered by the order above. It's a list - of tuples like [("value1", ""), ("value2", "")] where each tuple contains the name + `groups` is the dicts of groups, ordered by the order above. Its keys are + tuples like [("value1", ""), ("value2", "")] where each tuple contains the name of a single dimension-group """ orders = {} if "category_orders" not in args else args["category_orders"].copy() # figure out orders and what the single group name would be if there were one single_group_name = [] + unique_cache = dict() for col in grouper: if col == one_group: single_group_name.append("") else: - uniques = list(args["data_frame"][col].unique()) + if col not in unique_cache: + unique_cache[col] = list(args["data_frame"][col].unique()) + uniques = unique_cache[col] if len(uniques) == 1: single_group_name.append(uniques[0]) if col not in orders: orders[col] = uniques else: orders[col] = list(OrderedDict.fromkeys(list(orders[col]) + uniques)) - + df = args["data_frame"] if len(single_group_name) == len(grouper): # we have a single group, so we can skip all group-by operations! - grouped = None - sorted_group_names = [tuple(single_group_name)] + groups = {tuple(single_group_name): df} else: - grouped = args["data_frame"].groupby(grouper, sort=False) - sorted_group_names = [] - for group_name in grouped.groups: - if len(grouper) == 1: - group_name = (group_name,) - sorted_group_names.append(group_name) + group_indices = df.groupby(grouper, sort=False).indices + sorted_group_names = [g if len(grouper) != 1 else (g,) for g in group_indices] for i, col in reversed(list(enumerate(grouper))): if col != one_group: @@ -1951,7 +1949,9 @@ def get_orderings(args, grouper): if g[i] in orders[col] else -1, ) - return grouped, orders, sorted_group_names + + groups = {s: df.iloc[group_indices[s]] for s in sorted_group_names} + return groups, orders def make_figure(args, constructor, trace_patch=None, layout_patch=None): @@ -1970,7 +1970,7 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None): args, constructor, trace_patch, layout_patch ) grouper = [x.grouper or one_group for x in grouped_mappings] or [one_group] - grouped, orders, sorted_group_names = get_orderings(args, grouper) + groups, orders = get_groups_and_orders(args, grouper) col_labels = [] row_labels = [] @@ -1999,13 +1999,7 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None): trendline_rows = [] trace_name_labels = None facet_col_wrap = args.get("facet_col_wrap", 0) - for group_name in sorted_group_names: - if grouped is not None: - group = grouped.get_group( - group_name if len(group_name) > 1 else group_name[0] - ) - else: - group = args["data_frame"] + for group_name, group in groups.items(): mapping_labels = OrderedDict() trace_name_labels = OrderedDict() frame_name = "" From 949839699345726f59fde942a7273a1abe96303a Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Thu, 9 Jun 2022 13:18:45 -0400 Subject: [PATCH 07/11] slicing seems ambitious --- packages/python/plotly/plotly/express/_core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 34412eda744..d7b1b257c7a 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1938,7 +1938,8 @@ def get_groups_and_orders(args, grouper): # we have a single group, so we can skip all group-by operations! groups = {tuple(single_group_name): df} else: - group_indices = df.groupby(grouper, sort=False).indices + grouped = df.groupby(grouper, sort=False) + group_indices = grouped.indices sorted_group_names = [g if len(grouper) != 1 else (g,) for g in group_indices] for i, col in reversed(list(enumerate(grouper))): @@ -1950,7 +1951,7 @@ def get_groups_and_orders(args, grouper): else -1, ) - groups = {s: df.iloc[group_indices[s]] for s in sorted_group_names} + groups = {s: grouped.get_group(s) for s in sorted_group_names} return groups, orders From 35dbbe831feca8ad50e44e32c345b943b94e5a50 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Thu, 9 Jun 2022 13:34:45 -0400 Subject: [PATCH 08/11] trying again with groups --- packages/python/plotly/plotly/express/_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index d7b1b257c7a..630030eb97c 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1939,7 +1939,7 @@ def get_groups_and_orders(args, grouper): groups = {tuple(single_group_name): df} else: grouped = df.groupby(grouper, sort=False) - group_indices = grouped.indices + group_indices = grouped.groups sorted_group_names = [g if len(grouper) != 1 else (g,) for g in group_indices] for i, col in reversed(list(enumerate(grouper))): From cdae77cd7bea62b3bfb713daec1635bc145a2424 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Thu, 9 Jun 2022 14:03:53 -0400 Subject: [PATCH 09/11] possible fix --- packages/python/plotly/plotly/express/_core.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 630030eb97c..1f3a0d9e651 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1939,7 +1939,7 @@ def get_groups_and_orders(args, grouper): groups = {tuple(single_group_name): df} else: grouped = df.groupby(grouper, sort=False) - group_indices = grouped.groups + group_indices = grouped.indices sorted_group_names = [g if len(grouper) != 1 else (g,) for g in group_indices] for i, col in reversed(list(enumerate(grouper))): @@ -1951,7 +1951,9 @@ def get_groups_and_orders(args, grouper): else -1, ) - groups = {s: grouped.get_group(s) for s in sorted_group_names} + groups = { + s: grouped.get_group(s if len(s) > 1 else s[0]) for s in sorted_group_names + } return groups, orders @@ -2218,6 +2220,8 @@ def make_figure(args, constructor, trace_patch=None, layout_patch=None): fig.update_layout(layout_patch) if "template" in args and args["template"] is not None: fig.update_layout(template=args["template"], overwrite=True) + for f in frame_list: + f["name"] = str(f["name"]) fig.frames = frame_list if len(frames) > 1 else [] if args.get("trendline") and args.get("trendline_scope", "trace") == "overall": From b3a45831d89dd9d03514e26673f2aaf8cdfd039f Mon Sep 17 00:00:00 2001 From: jvdd Date: Fri, 10 Jun 2022 00:02:15 +0200 Subject: [PATCH 10/11] :broom: avoid unnecessary one_group groupby operations --- .../python/plotly/plotly/express/_core.py | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/python/plotly/plotly/express/_core.py b/packages/python/plotly/plotly/express/_core.py index 1f3a0d9e651..a73c0058ebb 100644 --- a/packages/python/plotly/plotly/express/_core.py +++ b/packages/python/plotly/plotly/express/_core.py @@ -1938,21 +1938,30 @@ def get_groups_and_orders(args, grouper): # we have a single group, so we can skip all group-by operations! groups = {tuple(single_group_name): df} else: - grouped = df.groupby(grouper, sort=False) + required_grouper = [g for g in grouper if g != one_group] + grouped = df.groupby(required_grouper, sort=False) # skip one_group groupers group_indices = grouped.indices - sorted_group_names = [g if len(grouper) != 1 else (g,) for g in group_indices] + sorted_group_names = [ + g if len(required_grouper) != 1 else (g,) for g in group_indices + ] - for i, col in reversed(list(enumerate(grouper))): - if col != one_group: - sorted_group_names = sorted( - sorted_group_names, - key=lambda g: orders[col].index(g[i]) - if g[i] in orders[col] - else -1, - ) + for i, col in reversed(list(enumerate(required_grouper))): + sorted_group_names = sorted( + sorted_group_names, + key=lambda g: orders[col].index(g[i]) if g[i] in orders[col] else -1, + ) + + # calculate the full group_names by inserting "" in the tuple index for one_group groups + full_sorted_group_names = [list(t) for t in sorted_group_names] + for i, col in enumerate(grouper): + if col == one_group: + for g in full_sorted_group_names: + g.insert(i, "") + full_sorted_group_names = [tuple(g) for g in full_sorted_group_names] groups = { - s: grouped.get_group(s if len(s) > 1 else s[0]) for s in sorted_group_names + sf: grouped.get_group(s if len(s) > 1 else s[0]) + for sf, s in zip(full_sorted_group_names, sorted_group_names) } return groups, orders From f46f809d1536c3c3d5e6959f18a2cf5a78b97b08 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Thu, 23 Jun 2022 09:41:00 -0400 Subject: [PATCH 11/11] changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe4557c8256..f7e55288e8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - - `pattern_shape` options now available in `px.timeline()` + - `pattern_shape` options now available in `px.timeline()` [#3774](https://github.com/plotly/plotly.py/pull/3774) + +### Performance + + - `px` methods no longer call `groupby` on the input dataframe when the result would be a single group, and no longer groups by a lambda, for significant speedups [#3765](https://github.com/plotly/plotly.py/pull/3765) ## [5.8.2] - 2022-06-10