pandas-dev / pandas

Flexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more
https://pandas.pydata.org
BSD 3-Clause "New" or "Revised" License
43.75k stars 17.96k forks source link

Indexing MultiIndex with duplicates return Series instead of raw value #42102

Open LucasG0 opened 3 years ago

LucasG0 commented 3 years ago

Code Sample, a copy-pastable example

single_index_no_duplicates = pd.Index([0, 1, 2])
single_index_with_duplicates = pd.Index([0, 1, 1])

multi_index_no_duplicates = pd.MultiIndex.from_tuples([(0, 0), (0, 1), (1, 0)])
multi_index_with_duplicates = pd.MultiIndex.from_tuples([(0, 0), (1, 0), (1, 0)])

>>> pd.Series(range(3), single_index_no_duplicates).loc[0]
0
>>> pd.Series(range(3), single_index_with_duplicates).loc[0]
0
>>> pd.Series(range(3), multi_index_no_duplicates).loc[(0,0)]
0
>>> pd.Series(range(3), multi_index_with_duplicates).loc[(0,0)]
0  0    0
dtype: int64

Problem description

When indexing a unique index value with .loc on a MultiIndex containing duplicated values, the return type is a Serieswhile it is a raw value for single Index.

Expected Output

I think we should expect a raw value to keep consistent with Index behavior.

>>> pd.Series(range(3), multi_index_with_duplicates).loc[(0,0)]
0

Output of pd.show_versions()

INSTALLED VERSIONS ------------------ commit : 2cb96529396d93b46abab7bbc73a208e708c642e python : 3.8.5.final.0 python-bits : 64 OS : Linux OS-release : 5.8.0-55-generic Version : #62~20.04.1-Ubuntu SMP Wed Jun 2 08:55:04 UTC 2021 machine : x86_64 processor : x86_64 byteorder : little LC_ALL : None LANG : fr_FR.UTF-8 LOCALE : fr_FR.UTF-8 pandas : 1.2.4 numpy : 1.20.3 pytz : 2019.3 dateutil : 2.7.3 pip : 20.0.2 setuptools : 45.2.0 Cython : None pytest : 6.2.4 hypothesis : None sphinx : None blosc : None feather : None xlsxwriter : None lxml.etree : None html5lib : 1.1 pymysql : None psycopg2 : None jinja2 : None IPython : None pandas_datareader: None bs4 : None bottleneck : None fsspec : None fastparquet : None gcsfs : None matplotlib : None numexpr : None odfpy : None openpyxl : None pandas_gbq : None pyarrow : None pyxlsb : None s3fs : None scipy : None sqlalchemy : None tables : None tabulate : None xarray : None xlrd : None xlwt : None numba : None
attack68 commented 3 years ago

slightly subjective what should happen here.

see #39775 for many similar cases, this is possibly a duplicate.

Dr-Irv commented 3 years ago

I think this is a bit different than #39775, which is more related to what happens if there are missing keys.

Here, the issue is that if the MultiIndex contains duplicates, and you ask for a key that is not duplicated in the index, you get a Series. If there are no duplicate keys in the MultiIndex, you get a single value. I ran into this in some code I was writing, where I knew there might be duplicates, or not, after some merging, and then the type of the result was inconsistent, dependent on whether there were duplicate keys in the index.

Here's another example:

import pandas as pd

df = pd.DataFrame(
    [["a", 1, 10], ["a", 1, 20], ["b", 2, 30]], columns=["ab", "ot", "val"]
).set_index(["ab", "ot"])
print(df)

s2 = df["val"].loc[("b", 2)]

print("result")
print(s2)
print()
print("s2 type", type(s2))
print()

df2 = pd.DataFrame(
    [["a", 1, 10], ["c", 1, 20], ["b", 2, 30]], columns=["ab", "ot", "val"]
).set_index(["ab", "ot"])
print(df2)

s3 = df2["val"].loc[("b", 2)]

print("result")
print(s3)
print()
print("s3 type", type(s3))

Here's the output:

       val
ab ot
a  1    10
   1    20
b  2    30
result
ab  ot
b   2     30
Name: val, dtype: int64

s2 type <class 'pandas.core.series.Series'>

       val
ab ot
a  1    10
c  1    20
b  2    30
result
30

s3 type <class 'numpy.int64'>

In both cases, I am doing .loc using the key ("b", 2). In the first case, other indices are duplicated, and I get a Series as the result. In the second case, no indices are duplicated, and I get a single value as the result.

So now I have to check the type of the result to determine what computation to do next. This is very unfriendly!

CharlesD38 commented 1 year ago

I can confirm this bug still exists for 2d DataFrame.

Copy Paste Example Below:

import pandas as pd

df = pd.DataFrame(
    index = pd.MultiIndex.from_tuples(
        [
            ('a', 'b'),
            ('a', 'b'),
            ('a', 'c'),
            ('b', 'c'),
            ('b', 'd')
        ]
    ),
    columns = ['Tens', 'Hundreds']
)
df['Tens'] = [10,20,30,40, 50]
df['Hundreds'] = [100,200,300,400, 500]

print(df)
print('\n\n')
print('Returning Series Instead of Value')
print(df.loc[('b', 'c'), 'Tens'])

df = pd.DataFrame(
    index = pd.MultiIndex.from_tuples(
        [
            ('a', 'b'),
            ('a', 'd'),
            ('a', 'c'),
            ('b', 'c'),
            ('b', 'd')
        ]
    ),
    columns = ['Tens', 'Hundreds']
)
df['Tens'] = [10,20,30,40, 50]
df['Hundreds'] = [100,200,300,400, 500]

print()
print(df)
print('\n\n')
print('Returning Series Instead of Value')
print(df.loc[('a', 'c'), 'Tens'])

Output:


     Tens  Hundreds
a b    10       100
  b    20       200
  c    30       300
b c    40       400
  d    50       500

Returning Series Instead of Value
b  c    40
Name: Tens, dtype: int64
     Tens  Hundreds
a b    10       100
  d    20       200
  c    30       300
b c    40       400
  d    50       500

Returning Value Instead of Series
30