在多索引数据中插入一行

Insert a row in a multiindex data

我有一个多索引数据框(行上的多索引),我想在特定行号处插入一行。这个问题之前已经被问过了(),但我有一些不同的问题想解决

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

因为 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 的行再次添加这些级别,这将把所有内容搞砸(而且我不能再切片了)

我找到的解决方案有效,但很难看。我没有使用以下例程

从空数据帧开始构建第 1 部分和第 2 部分,而不是对初始数据帧进行切片
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

在下面编辑我的评论

两个答案都正确。事实上,在我的真实情况下,情况要复杂一些。在这个例子中,所有的边标签都是 A-B 的顺序,但实际上这可以改变,我也想自由地强加不同的顺序。如果考虑到这一点,第一个 unstack/stack 的答案将不起作用。取消堆叠后,我不能再施加不同的顺序 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[:'label2'])

你再次得到 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

如果您只想在某个位置插入一组新数据,请尝试以下操作。使用 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'])