排序的多索引不正确后的python pandas groupby

时间:2015-08-14 08:24:16

标签: python sorting pandas

亲爱的蟒蛇/熊猫专家

我遇到了对多索引pandas数据帧的索引进行排序的问题。更具体地说,似乎排序有效,但之后的分组操作是“忘记”。再次排序。供参考:我正在运行python 3.4.3(由anaconda)和pandas 0.16.2(np19py34_0)。

现在详细说明我要做的事情。

我创建了一个多索引数据框

import pandas as pd
label="sdjks"
sidechar="B"
mi_level_fields = (label, sidechar)
mi_level_names = ["Label", "Side"]
pipeinfo_index = pd.MultiIndex.from_tuples([mi_level_fields], names=mi_level_names)

pipeinfoDF = pd.DataFrame(index=pipeinfo_index, columns=[])
pipeinfoDF.ix[(label, sidechar), "Nc"] = 10
pipeinfoDF.ix[(label, "C"), "Nc"] = 10
pipeinfoDF.ix[("ztest", "C"), "Nc"] = 400
pipeinfoDF.ix[("ztest", "B"), "Nc"] = 400
pipeinfoDF.ix[("yaki", "B"), "Nc"] = 1
pipeinfoDF.ix[("yaki", "C"), "Nc"] = 1

此pipeinfoDF数据框现在看起来像

             Nc
Label Side     
sdjks B      10
      C      10
ztest C     400
      B     400
yaki  B       1
      C       1

现在我想对数据框的索引进行排序,使NC列按升序排列。这可以通过

完成
pipeinfoDF.sort_index(by=["Nc"], inplace=True, ascending=True)

确实正确使用

print(pipeinfoDF.head())

             Nc
Label Side     
yaki  B       1
      C       1
sdjks B      10
      C      10
ztest C     400
      B     400

然而,当我想循环遍历这个多索引数据帧的行时会出现问题,我通常会这样做

for (label, df) in pipeinfoDF.groupby(level=0, sort=False):
    side_list = df.index.get_level_values('Side')
    for side in side_list:
        data = pipeinfoDF.ix[(label, side)]
        print(label, side, data.Nc)

现在作为输出

sdjks B 10.0
sdjks C 10.0
ztest C 400.0
ztest B 400.0
yaki B 1.0
yaki C 1.0

如你所见,虽然head()语句显示数据框已经正确排序,而循环索引(我通常将数据复制到另一个表)似乎没有使用正确的排序索引。

这对我来说似乎是一个错误:groupby语句中的sort选项对结果没有影响,并且报告了类似的事情here

现在我的问题是:有一种简单的方法可以解决这个问题吗? head语句似乎正确地给出了我的排序多索引数据帧,所以我一直试图复制这个头的输出,如

result = pipeinfoDF.head()

但这似乎不起作用。

我唯一的最后一次尝试尝试根据重置的索引创建一个新的数据框:

tmp = pipeinfoDF.copy()
tmp.reset_index(inplace=True)

lbls = tmp.Label.values
sds  = tmp.Side.values

pipeinfo_index2 = pd.MultiIndex.from_tuples(list(zip(lbls,sds)), names=mi_level_names)
pipeinfoDF2 = pd.DataFrame(index=pipeinfo_index2, columns=[])

for index, row in tmp.iterrows():
    for col in tmp.columns[2:]:
        pipeinfoDF2.ix[(row["Label"], row["Side"]), col] = row[col]

再次使用head()我得到正确的结果

             Nc
Label Side     
yaki  B       1
      C       1
sdjks B      10
      C      10
ztest C     400

但是如前面的多索引框架上的循环再次对第一组进行排序,我明确地使用sort = False进行排序

for (label, df) in pipeinfoDF2.groupby(level=0, sort=False):
    side_list = df.index.get_level_values('Side')
    for side in side_list:
        data = pipeinfoDF2.ix[(label, side)]
        print(label, side, data.Nc)

这个给予

sdjks B 10.0
sdjks C 10.0
yaki B 1.0
yaki C 1.0
ztest C 400.0
ztest B 400.0

所以groupby选项似乎再次对第一个索引进行排序。

编辑:我找到了以下内容来解决此问题。如果您打印数据框的索引,则它的标签不是按数字顺序排列的:

print(pipeinfoDF2.index)

MultiIndex(levels=[['sdjks', 'yaki', 'ztest'], ['B', 'C']],
           labels=[[1, 1, 0, 0, 2, 2], [0, 1, 0, 1, 1, 0]],
           names=['Label', 'Side'])

这里的关卡是' sdjks'' yaki' ' ZTEST'并且标签对应于订单1,1,0,0,2,2 在绘制第一个已排序的pipeinfoDF的索引时可以看到相同的情况,其中sort_index保持MultiIndex中的级别顺序,但只更改标签的顺序。

因此我可以通过强制标签运行为0,0,1,1,2,2来解决我的问题,因为显然groupby忽略了标签的顺序并总是选择级别的顺序。因此,我的解决方案是

pipeinfo_index2 = pd.MultiIndex.from_tuples([tuples[0]], names=mi_level_names)
pipeinfoDF2 = pd.DataFrame(index=pipeinfo_index2, columns=[])

然后像我之前那样填充其余的字段。这样,多索引看起来像

MultiIndex(levels=[['yaki', 'sdjks', 'ztest'], ['B', 'C']],
           labels=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 1, 0]],
           names=['Label', 'Side'])

现在使用groupby循环遍历行提供以下输出

yaki B 1.0
yaki C 1.0
sdjks B 10.0
sdjks C 10.0
ztest C 400.0
ztest B 400.0

哪个是对的!

所以我发现了一个非常难看的工作:在排序之后,将整个数据帧复制到一个新数据框,重置索引,然后将所有内容复制回来以强制执行MultiIndex标签的数字顺序。但我认为这是非常低效的,并产生大量的代码,我相信它可以更有效地做到这一点。

我的问题是:有没有办法循环考虑标签顺序的多索引数据框的行?它显然被忽略了。我错过了什么吗?希望有一种更简单的方法可以做到这一点。

任何提示都赞赏!

编辑:

Firelynx的建议有效。如果我做

for (label,side) in pipeinfoDF.index:
    data = pipeinfoDF.ix[(label, side)]
    print(label, side, data.Nc)

在第一次排序后,我正确地按排序顺序获取数据

yaki B 1.0
yaki C 1.0
sdjks B 10.0
sdjks C 10.0
ztest C 400.0
ztest B 400.0

保存了很多编码。然而,剩下的问题是:如果groupby与sort = False选项不产生相同的结果?这是一个错误,还是我错过了使用groupby方式循环我的数据框。它基于我用Google搜索的示例,但应谨慎使用。 无论如何,现在我解决了我的问题,我将放弃我的groupby访问数据的方式。

编辑:

Firelynx的解决方案有效,但它不再考虑多级结构,而只是将所有标签和侧面级别放入一个列表中。

为了获得与我想用groupby方法进行非常相似的东西,我现在做以下hack

label_list = []
for (label,side) in pipeinfoDF.index:
    if not label in label_list:
        label_list.append(label)

for label in label_list:
    df = pipeinfoDF.loc[label]
    side_list = df.index.get_level_values('Side')
    for side in side_list:
        data = pipeinfoDF.ix[(label, side)]
        print(label, side, data.Nc)

哪个正确产生

yaki B 1.0
yaki C 1.0
sdjks B 10.0
sdjks C 10.0
ztest C 400.0
ztest B 400.0

所以我首先使用Firelynx的建议提取已排序的标签列表,然后循环遍历此列表以获取每个标签的一面并执行我想要做的事情。虽然这比我的第一种方法更清洁,但我仍然觉得它可以更直接地以某种方式完成。我无法想象您不能在排序的多索引数据帧上使用groupby方法而不会弄乱排序顺序。也许有人有建议?无论如何,现在我对解决方案很满意

根据Firelynx的最新建议,我有一个小的更新,使它更清洁。但是,您需要保留一个列表以防止标签的重复计数,因为唯一适用于唯一(标签,侧面)组合。所以我现在有了

label_list = []
for (label, side) in pipeinfoDF.index.unique():
    if not label in label_list:
        label_list.append(label)
    else:
        continue
    df = pipeinfoDF.loc[label]
    side_list = df.index.get_level_values('Side')
    for side in side_list:
        data = pipeinfoDF.ix[(label, side)]
        print(label, side, data.Nc)

是否可以单独在标签上使用unique()?然后我可以删除label_list以跟踪已经处理的标签

1 个答案:

答案 0 :(得分:1)

您的for循环超过.groupby(level=0, ...,,您只是在level=0进行分组,因此您的结果数据集将仅在索引的第一级进行排序。

您可以这样做:

for label in pipedinfoDF.index.unique():
    group = pipedinfoDF.loc[label]

获得您想要的订单。