@@ -438,6 +438,9 @@ class Grouping:
438
438
* groups : dict of {group -> label_list}
439
439
"""
440
440
441
+ _codes : np .ndarray | None = None
442
+ _group_index : Index | None = None
443
+
441
444
def __init__ (
442
445
self ,
443
446
index : Index ,
@@ -461,27 +464,25 @@ def __init__(
461
464
self .in_axis = in_axis
462
465
self .dropna = dropna
463
466
467
+ self ._passed_categorical = False
468
+
464
469
# right place for this?
465
470
if isinstance (grouper , (Series , Index )) and name is None :
466
471
self .name = grouper .name
467
472
468
473
# we have a single grouper which may be a myriad of things,
469
474
# some of which are dependent on the passing in level
470
475
471
- if level is not None :
472
- if not isinstance (level , int ):
473
- if level not in index .names :
474
- raise AssertionError (f"Level { level } not in index" )
475
- level = index .names .index (level )
476
-
476
+ ilevel = self ._ilevel
477
+ if ilevel is not None :
477
478
if self .name is None :
478
- self .name = index .names [level ]
479
+ self .name = index .names [ilevel ]
479
480
480
481
(
481
- self .grouper ,
482
+ self .grouper , # Index
482
483
self ._codes ,
483
484
self ._group_index ,
484
- ) = index ._get_grouper_for_level (self .grouper , level )
485
+ ) = index ._get_grouper_for_level (self .grouper , ilevel )
485
486
486
487
# a passed Grouper like, directly get the grouper in the same way
487
488
# as single grouper groupby, use the group_info to get codes
@@ -502,32 +503,13 @@ def __init__(
502
503
self .grouper = grouper ._get_grouper ()
503
504
504
505
else :
505
-
506
506
# a passed Categorical
507
507
if is_categorical_dtype (self .grouper ):
508
+ self ._passed_categorical = True
508
509
509
510
self .grouper , self .all_grouper = recode_for_groupby (
510
511
self .grouper , self .sort , observed
511
512
)
512
- categories = self .grouper .categories
513
-
514
- # we make a CategoricalIndex out of the cat grouper
515
- # preserving the categories / ordered attributes
516
- self ._codes = self .grouper .codes
517
- if observed :
518
- codes = algorithms .unique1d (self .grouper .codes )
519
- codes = codes [codes != - 1 ]
520
- if sort or self .grouper .ordered :
521
- codes = np .sort (codes )
522
- else :
523
- codes = np .arange (len (categories ))
524
-
525
- self ._group_index = CategoricalIndex (
526
- Categorical .from_codes (
527
- codes = codes , categories = categories , ordered = self .grouper .ordered
528
- ),
529
- name = self .name ,
530
- )
531
513
532
514
# we are done
533
515
elif isinstance (self .grouper , Grouping ):
@@ -564,8 +546,20 @@ def __repr__(self) -> str:
564
546
def __iter__ (self ):
565
547
return iter (self .indices )
566
548
567
- _codes : np .ndarray | None = None
568
- _group_index : Index | None = None
549
+ @cache_readonly
550
+ def _ilevel (self ) -> int | None :
551
+ """
552
+ If necessary, converted index level name to index level position.
553
+ """
554
+ level = self .level
555
+ if level is None :
556
+ return None
557
+ if not isinstance (level , int ):
558
+ index = self .index
559
+ if level not in index .names :
560
+ raise AssertionError (f"Level { level } not in index" )
561
+ return index .names .index (level )
562
+ return level
569
563
570
564
@property
571
565
def ngroups (self ) -> int :
@@ -582,6 +576,12 @@ def indices(self):
582
576
583
577
@property
584
578
def codes (self ) -> np .ndarray :
579
+ if self ._passed_categorical :
580
+ # we make a CategoricalIndex out of the cat grouper
581
+ # preserving the categories / ordered attributes
582
+ cat = self .grouper
583
+ return cat .codes
584
+
585
585
if self ._codes is None :
586
586
self ._make_codes ()
587
587
# error: Incompatible return value type (got "Optional[ndarray]",
@@ -592,12 +592,33 @@ def codes(self) -> np.ndarray:
592
592
def result_index (self ) -> Index :
593
593
if self .all_grouper is not None :
594
594
group_idx = self .group_index
595
- assert isinstance (group_idx , CategoricalIndex ) # set in __init__
595
+ assert isinstance (group_idx , CategoricalIndex )
596
596
return recode_from_groupby (self .all_grouper , self .sort , group_idx )
597
597
return self .group_index
598
598
599
- @property
599
+ @cache_readonly
600
600
def group_index (self ) -> Index :
601
+ if self ._passed_categorical :
602
+ # we make a CategoricalIndex out of the cat grouper
603
+ # preserving the categories / ordered attributes
604
+ cat = self .grouper
605
+ categories = cat .categories
606
+
607
+ if self .observed :
608
+ codes = algorithms .unique1d (cat .codes )
609
+ codes = codes [codes != - 1 ]
610
+ if self .sort or cat .ordered :
611
+ codes = np .sort (codes )
612
+ else :
613
+ codes = np .arange (len (categories ))
614
+
615
+ return CategoricalIndex (
616
+ Categorical .from_codes (
617
+ codes = codes , categories = categories , ordered = cat .ordered
618
+ ),
619
+ name = self .name ,
620
+ )
621
+
601
622
if self ._group_index is None :
602
623
self ._make_codes ()
603
624
assert self ._group_index is not None
0 commit comments