@@ -2670,68 +2670,80 @@ def _ixs(self, i, axis=0):
2670
2670
def __getitem__ (self , key ):
2671
2671
key = com ._apply_if_callable (key , self )
2672
2672
2673
- # shortcut if we are an actual column
2674
- is_mi_columns = isinstance (self .columns , MultiIndex )
2673
+ # shortcut if the key is in columns
2675
2674
try :
2676
- if key in self .columns and not is_mi_columns :
2677
- return self ._getitem_column (key )
2678
- except :
2675
+ if self .columns .is_unique and key in self .columns :
2676
+ if self .columns .nlevels > 1 :
2677
+ return self ._getitem_multilevel (key )
2678
+ return self ._get_item_cache (key )
2679
+ except (TypeError , ValueError ):
2680
+ # The TypeError correctly catches non hashable "key" (e.g. list)
2681
+ # The ValueError can be removed once GH #21729 is fixed
2679
2682
pass
2680
2683
2681
- # see if we can slice the rows
2684
+ # Do we have a slicer (on rows)?
2682
2685
indexer = convert_to_index_sliceable (self , key )
2683
2686
if indexer is not None :
2684
- return self ._getitem_slice (indexer )
2687
+ return self ._slice (indexer , axis = 0 )
2685
2688
2686
- if isinstance (key , (Series , np .ndarray , Index , list )):
2687
- # either boolean or fancy integer index
2688
- return self ._getitem_array (key )
2689
- elif isinstance (key , DataFrame ):
2689
+ # Do we have a (boolean) DataFrame?
2690
+ if isinstance (key , DataFrame ):
2690
2691
return self ._getitem_frame (key )
2691
- elif is_mi_columns :
2692
- return self ._getitem_multilevel (key )
2692
+
2693
+ # Do we have a (boolean) 1d indexer?
2694
+ if com .is_bool_indexer (key ):
2695
+ return self ._getitem_bool_array (key )
2696
+
2697
+ # We are left with two options: a single key, and a collection of keys,
2698
+ # We interpret tuples as collections only for non-MultiIndex
2699
+ is_single_key = isinstance (key , tuple ) or not is_list_like (key )
2700
+
2701
+ if is_single_key :
2702
+ if self .columns .nlevels > 1 :
2703
+ return self ._getitem_multilevel (key )
2704
+ indexer = self .columns .get_loc (key )
2705
+ if is_integer (indexer ):
2706
+ indexer = [indexer ]
2693
2707
else :
2694
- return self ._getitem_column (key )
2708
+ if is_iterator (key ):
2709
+ key = list (key )
2710
+ indexer = self .loc ._convert_to_indexer (key , axis = 1 ,
2711
+ raise_missing = True )
2695
2712
2696
- def _getitem_column (self , key ):
2697
- """ return the actual column """
2713
+ # take() does not accept boolean indexers
2714
+ if getattr (indexer , "dtype" , None ) == bool :
2715
+ indexer = np .where (indexer )[0 ]
2698
2716
2699
- # get column
2700
- if self .columns .is_unique :
2701
- return self ._get_item_cache (key )
2717
+ data = self ._take (indexer , axis = 1 )
2702
2718
2703
- # duplicate columns & possible reduce dimensionality
2704
- result = self ._constructor (self ._data .get (key ))
2705
- if result .columns .is_unique :
2706
- result = result [key ]
2719
+ if is_single_key :
2720
+ # What does looking for a single key in a non-unique index return?
2721
+ # The behavior is inconsistent. It returns a Series, except when
2722
+ # - the key itself is repeated (test on data.shape, #9519), or
2723
+ # - we have a MultiIndex on columns (test on self.columns, #21309)
2724
+ if data .shape [1 ] == 1 and not isinstance (self .columns , MultiIndex ):
2725
+ data = data [key ]
2707
2726
2708
- return result
2709
-
2710
- def _getitem_slice (self , key ):
2711
- return self ._slice (key , axis = 0 )
2727
+ return data
2712
2728
2713
- def _getitem_array (self , key ):
2729
+ def _getitem_bool_array (self , key ):
2714
2730
# also raises Exception if object array with NA values
2715
- if com .is_bool_indexer (key ):
2716
- # warning here just in case -- previously __setitem__ was
2717
- # reindexing but __getitem__ was not; it seems more reasonable to
2718
- # go with the __setitem__ behavior since that is more consistent
2719
- # with all other indexing behavior
2720
- if isinstance (key , Series ) and not key .index .equals (self .index ):
2721
- warnings .warn ("Boolean Series key will be reindexed to match "
2722
- "DataFrame index." , UserWarning , stacklevel = 3 )
2723
- elif len (key ) != len (self .index ):
2724
- raise ValueError ('Item wrong length %d instead of %d.' %
2725
- (len (key ), len (self .index )))
2726
- # check_bool_indexer will throw exception if Series key cannot
2727
- # be reindexed to match DataFrame rows
2728
- key = check_bool_indexer (self .index , key )
2729
- indexer = key .nonzero ()[0 ]
2730
- return self ._take (indexer , axis = 0 )
2731
- else :
2732
- indexer = self .loc ._convert_to_indexer (key , axis = 1 ,
2733
- raise_missing = True )
2734
- return self ._take (indexer , axis = 1 )
2731
+ # warning here just in case -- previously __setitem__ was
2732
+ # reindexing but __getitem__ was not; it seems more reasonable to
2733
+ # go with the __setitem__ behavior since that is more consistent
2734
+ # with all other indexing behavior
2735
+ if isinstance (key , Series ) and not key .index .equals (self .index ):
2736
+ warnings .warn ("Boolean Series key will be reindexed to match "
2737
+ "DataFrame index." , UserWarning , stacklevel = 3 )
2738
+ elif len (key ) != len (self .index ):
2739
+ raise ValueError ('Item wrong length %d instead of %d.' %
2740
+ (len (key ), len (self .index )))
2741
+
2742
+ # check_bool_indexer will throw exception if Series key cannot
2743
+ # be reindexed to match DataFrame rows
2744
+ key = check_bool_indexer (self .index , key )
2745
+ indexer = key .nonzero ()[0 ]
2746
+ return self ._take (indexer , axis = 0 )
2735
2747
2736
2748
def _getitem_multilevel (self , key ):
2737
2749
loc = self .columns .get_loc (key )
0 commit comments