python - Pandas:如何在 python3 中对混合类型的多索引使用切片?

标签 python python-3.x pandas sorting

正如我在 this partially related question 中指出的那样,不可能再对混合类型的序列进行排序:

# Python3.6
sorted(['foo', 'bar', 10, 200, 3])
# => TypeError: '<' not supported between instances of 'str' and 'int'

这会影响 pandas 中的切片查询。以下示例说明了我的问题。

import pandas as pd
import numpy as np
index = [(10,3),(10,1),(2,2),('foo',4),('bar',5)]
index = pd.MultiIndex.from_tuples(index)
data = np.random.randn(len(index),2)
table = pd.DataFrame(data=data, index=index)

idx=pd.IndexSlice
table.loc[idx[:10,:],:]
# The last line will raise an UnsortedIndexError because 
# 'foo' and 'bar' appear in the wrong order.

异常信息如下:

UnsortedIndexError: 'MultiIndex slicing requires the index to be lexsorted: slicing on levels [0], lexsort depth 0'

在 python2.x 中,我通过对索引进行 lex 排序从这个异常中恢复:

# Python2.x:
table = table.sort_index()

#               0         1
# 2   2  0.020841  0.717178
# 10  1  1.608883  0.807834
#     3  0.566967  1.978718
# bar 5 -0.683814 -0.382024
# foo 4  0.150284 -0.750709

table.loc[idx[:10,:],:]
#              0         1
# 2  2  0.020841  0.717178
# 10 1  1.608883  0.807834
#    3  0.566967  1.978718

但是,在 python3 中,我以开头提到的异常结束:

TypeError: '<' not supported between instances of 'str' and 'int'

如何从中恢复?在排序之前将索引转换为字符串不是一种选择,因为这会破坏索引的正确排序:

# Python2/3
index = [(10,3),(10,1),(2,2),('foo',4),('bar',5)]
index = list(map(lambda x: tuple(map(str,x)), index))
index = pd.MultiIndex.from_tuples(index)
data = np.random.randn(len(index),2)
table = pd.DataFrame(data=data, index=index)
table = table.sort_index()
#               0         1
# 10  1  0.020841  0.717178
#     3  1.608883  0.807834
# 2   2  0.566967  1.978718
# bar 5 -0.683814 -0.382024
# foo 4  0.150284 -0.750709

通过这种排序,基于值的切片将被打破。

table.loc[idx[:10,:],:]     # Raises a TypeError
table.loc[idx[:'10',:],:]   # Misses to return the indices [2,:]

我该如何恢复?

最佳答案

这是我能想到的最好的。分三步解决:

  • 以 lex 排序保留 python2 中旧的混合类型排序的方式对多索引进行字符串化。例如,int 可以在前面加上足够多的 0。
  • 对表格进行排序。
  • 使用切片访问表时使用相同的字符串化。

代码如下(完整示例):

import numpy as np
import pandas as pd 

# Stringify whatever needs to be converted.
# In this example: only ints are stringified.
def toString(x):
    if isinstance(x,int):
        x = '%03d' % x
    return x
# Stringify an index tuple.
def idxToString(idx):
    if isinstance(idx, tuple):
        idx = list(idx)
        for i,x in enumerate(idx):
            idx[i] = toString(x)
        return tuple(idx)
    else:
        return toString(idx)
# Replacement for pd.IndexSlice
class IndexSlice(object):
    @staticmethod
    def _toString(arg):
        if isinstance(arg, slice):
            arg = slice(toString(arg.start),
                        toString(arg.stop),
                        toString(arg.step))
        else:
            arg = toString(arg)
        return arg

    def __getitem__(self, arg):
        if isinstance(arg, tuple):
            return tuple(map(self._toString, arg))
        else:
            return self._toString(arg)

# Build the table.
index = [(10,3),(10,1),(2,2),('foo',4),('bar',5)]
index = pd.MultiIndex.from_tuples(index)
data = np.random.randn(len(index),2)
table = pd.DataFrame(data=data, index=index)
# 1) Stringify the index.
table.index = table.index.map(idxToString)
# 2) Sort the index.
table = table.sort_index()
# 3) Create an IndexSlice that applies the same
#    stringification rules. (Replaces pd.IndexSlice)
idx = IndexSlice()
# Now, the table rows can be accessed as usual.
table.loc[idx[10],:]
table.loc[idx[:10],:]
table.loc[idx[:'bar',:],:]
table.loc[idx[:,:2],:]

这不是很漂亮,但它修复了升级到 python3 后损坏的表数据的基于切片的访问。如果你们有任何更好的建议,我很高兴阅读。

关于python - Pandas:如何在 python3 中对混合类型的多索引使用切片?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50098443/

相关文章:

python - 读入文件 - 更改内容 - 写出到同一个文件

python - 为什么 fields.Url ('...' ) 根据渲染字段的数量中断 Flask-RESTful

python - 对 Pandas 中的数据进行分组

python - 当条件为真时,Pandas 将数据帧拆分为多个

python - 如果一行中有多个关键字,是否有可能分隔关键字

python - 检查变量是否是数据框

python - 美丽汤找不到标签

python - 将 Pandas DataFrame 转换为稀疏矩阵

python - 正确输入 sys.stdout 和文件

python - Pandas 为一列分配不同的值取决于另一列中的值