Skip to content

Commit af6d551

Browse files
committed
refactor(_utils): simplify NaN id recoding for categoricals in _id_var
Both the drop and no-drop paths now share one recoding of the categorical codes, and the non-categorical path uses match(start=1) instead of a manual increment.
1 parent b291e68 commit af6d551

2 files changed

Lines changed: 26 additions & 21 deletions

File tree

plotnine/_utils/__init__.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -355,48 +355,53 @@ def len_unique(x):
355355

356356
def _id_var(x: AnyArrayLike, drop: bool = False) -> list[int]:
357357
"""
358-
Assign ids to items in x. If two items
359-
are the same, they get the same id.
358+
Assign ids to items in x
359+
360+
If two items are the same, they get the same id.
361+
The ids start at 1 and NaNs have the highest id.
360362
361363
Parameters
362364
----------
363365
x : array_like
364366
items to associate ids with
365367
drop : bool
366368
Whether to drop unused factor levels
369+
370+
Returns
371+
-------
372+
ids:
373+
List of ids
367374
"""
368375
if len(x) == 0:
369376
return []
370377

371378
if isinstance(x, pd.Series) and array_kind.categorical(x):
379+
# The ids are a "re-coding" of the categorical codes/levels
380+
# to meet the output requirements.
372381
if drop:
373382
x = x.cat.remove_unused_categories()
374-
lst = list(x.cat.codes + 1)
375-
if 0 in lst:
376-
new_nan_code = max(lst) + 1
377-
lst = [val if val != 0 else new_nan_code for val in lst]
378-
else:
379-
has_nan = any(np.isnan(i) for i in x if isinstance(i, float))
380-
if has_nan:
381-
# NaNs are -1, we give them the highest code
382-
nan_code = -1
383-
new_nan_code = np.max(x.cat.codes) + 1
384-
# TODO: We are assuming that x is of type Sequence[int|nan]
385-
# is that accurate.
386-
lst = [val if val != nan_code else new_nan_code for val in x]
387-
else:
388-
lst = list(x.cat.codes + 1)
383+
384+
codes = x.cat.codes
385+
386+
# We want our list to start at 1
387+
# But NaNs are -1, and if we have them we want them to have,
388+
# the highest code. i.e. to be ordered last.
389+
ids = list(codes + 1)
390+
has_nan = min(codes) == -1
391+
if has_nan:
392+
# The NaNs now have an id of 0
393+
highest_id = max(ids) + 1
394+
ids = [highest_id if i == 0 else i for i in ids]
389395
else:
390396
try:
391397
levels = sorted(set(x))
392398
except TypeError:
393399
# x probably has NANs
394400
levels = multitype_sort(list(set(x)))
395401

396-
lst = match(x, levels)
397-
lst = [item + 1 for item in lst]
402+
ids = list(match(x, levels, start=1))
398403

399-
return lst
404+
return ids
400405

401406

402407
def join_keys(x, y, by=None):

tests/test_facets.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ def test_facet_grid_with_missing_categorical_values():
235235
}
236236
)
237237

238-
p = ggplot(data, aes("x", "y")) + geom_point() + facet_grid("row ~ col")
238+
p = ggplot(data, aes("x", "y")) + geom_point() + facet_grid("row", "col")
239239

240240
# No exception
241241
p.draw_test() # pyright: ignore

0 commit comments

Comments
 (0)