在多索引数据

时间:2015-09-07 13:25:49

标签: python pandas insert

我有一个多索引数据框(行上的多索引),我想在某个行号插入一行。这个问题之前已被讨论过(How to insert a row in a Pandas multiindex dataframe?),但我想解决的问题有点不同

事实上。我解决了它,但它太丑了,我想问是否有更好的方法来做到这一点。

由于pandas中没有insert_row函数,我想通过首先使用切片将我的数据帧复制到某个行到新帧来插入一行,然后追加我要插入的行,然后追加其余的原始数据框。实际上,这种方法很有效。这是一个例子

首先,我使用

创建一个空数据框
pipeinfo_index = pd.MultiIndex.from_tuples([tuple([None, None])], names=["Label", "Side"])
pipeinfoDF = pd.DataFrame(index=pipeinfo_index, columns=[])
pipeinfoDF.drop(np.nan, level='Label', inplace=True)
for i in range(4):
    label = "label{}".format(i)
    pipeinfoDF.ix[(label, "A"),"COL1"] = i
    pipeinfoDF.ix[(label, "B"),"COL1"] = 2*i+1

看起来像

             COL1
Label  Side      
label0 A        0
       B        1
label1 A        1
       B        3
label2 A        2
       B        5
label3 A        3

我想在label1和label2之间添加一行,以便获得一个像

这样的新数据帧
             COL1
Label  Side      
label0 A        0
       B        1
label1 A        1
       B        3
oker5  A        10
       B        30
label2 A        2
       B        5
label3 A        3

在我的第一种方法中,我按照上述策略执行此操作

# copy first half to temporary data frame
part1= pipeinfoDF.ix[:"label1"].copy()
# copy second half to temporary data frame
part2= pipeinfoDF.ix["label2":].copy()

# append new row to insert to first part
part1.ix[("oker5", "B"),"COL1"] = 10
part1.ix[("oker5", "A"),"COL2"] = 30

# append second half of row to new data frame
for label, side in part2.index:
     print("adding {} {}".format(label, side))
     part1.ix[(label, side),:] = part2.ix[(label, side),:]

# copy the new data frame to the initial data frame
pipeinfoDF = part1.copy()

事实上,这是有效的;如果我打印出pipeinfoDF,我会得到上面显示的数据框。但是,问题是:如果我想再次执行此操作(因为我想在此初始数据帧中添加多行),我会收到一条错误消息。即使数据帧的另一部分也会导致错误 例如,做

part3= pipeinfoDF2.loc[:"oker5"].copy()

导致错误:

Traceback (most recent call last):
  File "C:/Apps/JetBrains/PyCharm Community Edition 4.5.4/jre/jre/bin/DataEelco/.PyCharm/config/scratches/scratch", line 96, in <module>
    part3= pipeinfoDF2.loc[:"oker5"].copy()
  File "C:\Apps\Anaconda\Anaconda2\envs\py34\lib\site-packages\pandas\core\indexing.py", line 1189, in __getitem__
    return self._getitem_axis(key, axis=0)
  File "C:\Apps\Anaconda\Anaconda2\envs\py34\lib\site-packages\pandas\core\indexing.py", line 1304, in _getitem_axis
    return self._get_slice_axis(key, axis=axis)
  File "C:\Apps\Anaconda\Anaconda2\envs\py34\lib\site-packages\pandas\core\indexing.py", line 1211, in _get_slice_axis
    slice_obj.step)
  File "C:\Apps\Anaconda\Anaconda2\envs\py34\lib\site-packages\pandas\core\index.py", line 2340, in slice_indexer
    start_slice, end_slice = self.slice_locs(start, end, step=step, kind=kind)
  File "C:\Apps\Anaconda\Anaconda2\envs\py34\lib\site-packages\pandas\core\index.py", line 4990, in slice_locs
    return super(MultiIndex, self).slice_locs(start, end, step, kind=kind)
  File "C:\Apps\Anaconda\Anaconda2\envs\py34\lib\site-packages\pandas\core\index.py", line 2490, in slice_locs
    end_slice = self.get_slice_bound(end, 'right', kind)
  File "C:\Apps\Anaconda\Anaconda2\envs\py34\lib\site-packages\pandas\core\index.py", line 4961, in get_slice_bound
    return self._partial_tup_index(label, side=side)
  File "C:\Apps\Anaconda\Anaconda2\envs\py34\lib\site-packages\pandas\core\index.py", line 4996, in _partial_tup_index
    (len(tup), self.lexsort_depth))
KeyError: 'Key length (1) was greater than MultiIndex lexsort depth (0)'

我发现这个错误与我想要将数据行添加到已经存在的新数据框的事实有关

如果我这样做

print(part1)

我得到了

             COL1
Label  Side      
label0 A        0
       B        1
label1 A        1
       B        3

但如果我这样做

print(part1.index)

我看到了

MultiIndex(levels=[['label0', 'label1', 'label2', 'label3'], ['A', 'B']],
           labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
           names=['Label', 'Side'])

换句话说,如果我将行切片到label1,我确实得到一个看起来像这个切片的数据帧,但是多索引级别仍然包含更高级别的数据。显然,如果我试图通过复制part2df的行再次添加这些级别,这将搞乱一切(我不能再切片)

我找到的解决方案有效,但很难看。我没有切片初始数据框我使用以下例程从空数据框开始构造part1和part2

def get_slice(dataframe, start_from_label=None, end_at_label=None):
    # create a empty data frame and initialise with rows copy from the dataframe starting from
    # start_from_label and ending at end_at_label
    mi = pd.MultiIndex.from_tuples([tuple([None, None])], names=dataframe.index.names)

    df_new = pd.DataFrame(index=mi, columns=[])
    df_new.drop(np.nan, level='Label', inplace=True)

    insert = False
    for label, df in dataframe.groupby(level=0):
        side_list = df.index.get_level_values('Side')
        if start_from_label is None or label == start_from_label:
            insert = True
        if insert:
            for side in side_list:
                for col in dataframe.columns:
                    df_new.ix[(label, side),col] = dataframe.ix[(label, side),col]

        if end_at_label is not None and label == end_at_label:
            break

    return df_new

在主代码中,在我创建切片的地方,我现在做

# part1= pipeinfoDF.ix[:"label1"].copy()
part1 = get_slice(pipeinfoDF, end_at_label="label1")

# part2= pipeinfoDF.ix["label2":].copy()
part2 = get_slice(pipeinfoDF, start_from_label="label2")

所有其余代码保持不变。不同之处在于part1和part2具有干净的多索引字段。如果我打印索引我得

MultiIndex(levels=[['label0', 'label1'], ['A', 'B']],
           labels=[[0, 0, 1, 1], [0, 1, 0, 1]],
           names=['Label', 'Side'])

如果我知道再次对新数据帧进行切片,则可以正常工作:

part3= pipeinfoDF2.loc[:"oker5"].copy()
print(part3)

我得到了

             COL1
Label  Side      
label0 A        0
       B        1
label1 A        1
       B        3
oker5  B       30
       A       10

如您所见,结果数据帧上的下一个切片不再引发KeyError,这意味着我可以递归地向此数据帧添加更多行

我的问题现在如下:如何使用第一个切片方法,仍然能够再次切片结果数据框?我发现的解决方案现在基本上通过创建一个空数据框并逐个复制行来实现切片,但我想说它应该可以更好的方式。希望有人可以给我一些建议

Eelco

编辑我的评论

两个答案都是正确的。事实上,在我的实际情况中,它有点复杂。在该示例中,所有Side标签都是订单A-B,但实际上这可以改变,并且我希望可以自由地施加不同的订单。如果考虑到这一点,第一个使用unstack / stack的anwer不起作用。在拆散之后,我不能再强制执行不同的A / B或B / A.因此,我必须使用dropna选项。

示例,我的数据框略有不同

for i in range(4):
    label = "label{}".format(i)
    if i!=2:
        l1 = "A"
        l2 = "B"
    else:
        l1 = "B"
        l2 = "A"

    pipeinfoDF.ix[(label, l1),"COL1"] = i
    pipeinfoDF.ix[(label, l2),"COL1"] = 2*i+1
    pipeinfoDF.ix[(label, l1),"COL2"] = 10*i
    pipeinfoDF.ix[(label, l2),"COL2"] = 10*(2*i+1)

看起来像

             COL1  COL2
Label  Side            
label0 A        0     0
       B        1    10
label1 A        1    10
       B        3    30
label2 B        2    20
       A        5    50
label3 A        3    30
       B        7    70

现在,unstack方法不允许先插入一行,例如先插入B,然后插入A,因为在unstack / stack之后,顺序总是A / B.这不是我想要的。

所以其他解决方案实际上就是我需要的。有一个小问题,也许它也可以解决:-)

我的方法现在是:

# create the lines to add 
newdata = pd.DataFrame(index=pipeinfo_index, columns=[])
newdata.ix[('oker8', "B"), "COL1"] = 10
newdata.ix[('oker8', "A"lot, it ), "COL1"] = 30
newdata.ix[('oker8', "B"), "COL2"] = 108
newdata.ix[('oker8', "A"), "COL2"] = 300
newdata2 = pd.DataFrame(index=pipeinfo_index, columns=[])
newdata2.ix[('oker9', "A"), "COL1"] = 20
newdata2.ix[('oker9', "B"), "COL1"] = 50
newdata2.ix[('oker9', "A"), "COL2"] = 2lot, it 023
newdata2.ix[('oker9', "B"), "COL2"] = 5320

#get the indices to add the row
inx1=np.where(pipeinfoDF.reset_index().Label.values=='label1'); inx1=inx1[0][0]+2
inx2=np.where(pipeinfoDF.reset_index().Label.values=='label2'); inx2=inx2[0][0]

#insert the first data row
pipeinfoDF = pd.concat([pipeinfoDF.ix[:inx1], newdata, pipeinfoDF.ix[inx2:]])
pipeinfoDF.drop(np.nan, level='Label', inplace=True)

正确地给出了

             COL1  COL2
Label  Side            
label0 A        0     0
       B        1    10
label1 A        1    10
       B        3    30
oker8  B       10   108
       A       30   300
label2 B        2    20
       A        5    50
label3 A        3    30
       B        7    70

并且可以递归地添加第二行作为

inx1=np.where(pipeinfoDF.reset_index().Label.values=='label2'); inx1=inx1[0][0]+2
inx2=np.where(pipeinfoDF.reset_index().Label.values=='label3'); inx2=inx2[0][0]

pipeinfoDF = pd.concat([pipeinfoDF.ix[:inx1], newdata2, pipeinfoDF.ix[inx2:]])
pipeinfoDF.drop(np.nan, level='Label', inplace=True)
print(pipeinfoDF)

给予

             COL1  COL2
Label  Side            
label0 A        0     0
       B        1    10
label1 A        1    10
       B        3    30
oker8  B       10   108
       A       30   300
label2 B        2    20
       A        5    50
oker9  A       20  2023
       B       50  5320
label3 A        3    30
       B        7    70

所以这很有效。我唯一不理解的是,一旦开始使用索引切片,就不可能使用标签而不是索引进行切片。例如,如果你这样做

打印(pipeinfoDF.ix [:&#39; LABEL2&#39;])

你再次获得了KeyError,而在脚本开始时,在创建索引的第一个切片之前,带有标签的切片工作得很好:你正确得到了

            COL1  COL2
Label  Side            
label0 A        0     0
       B        1    10
label1 A        1    10
       B        3    30
label2 B        2    20
       A        5    50

我不确定这是否会在以后给我带来麻烦。但也许有办法解决这个问题呢?好吧,到目前为止:非常感谢两个!

Eelco

1 个答案:

答案 0 :(得分:1)

如果您只想在某个位置插入一组新数据,请尝试以下操作。有了drop,你会收到一个新对象,不再有KeyError问题了。

# create new dataframe based on pipeinfo_index
newdata = pd.DataFrame(index=pipeinfo_index, columns=[])
newdata.ix[('oaker', "A"), "COL1"] = 10
newdata.ix[('oaker', "B"), "COL1"] = 30

idx = getindexof('label1')

pipeinfoDF = pd.concat([pipeinfoDF.ix[:idx], newdata]) #, pipeinfoDF.ix[idx:]])
# drop the NaN and recieve a new object
pipeinfoDF.drop(np.nan, level='Label', inplace=True)

pipeinfoDF.index
MultiIndex(levels=[[u'label0', u'label1', u'oaker'], [u'A', u'B']],
       labels=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]],
       names=[u'Label', u'Side'])