熊猫:如何有效地创建子索引?

Zhe*_*Sun 6 python performance pandas

我想根据索引为我的数据帧创建一个子索引.例如,我有一个这样的数据帧:

      Content        Date
ID                       
Bob  birthday  2010.03.01
Bob    school  2010.04.01
Tom  shopping  2010.02.01
Tom      work  2010.09.01
Tom   holiday  2010.10.01
Run Code Online (Sandbox Code Playgroud)

我想为我创建一个子索引,ID结果数据框如下所示:

               Content        Date
ID  subindex                      
Bob 1         birthday  2010.03.01
    2           school  2010.04.01
Tom 1         shopping  2010.02.01
    2             work  2010.09.01
    3          holiday  2010.10.01
Run Code Online (Sandbox Code Playgroud)

要做到这一点,我需要先创建我的subindex列表.我在帮助文档中搜索,似乎最简洁的方法是使用transform:

subindex = df['Date'].groupby(df.index).transform(lambda x: np.arange(1, len(x) + 1))
Run Code Online (Sandbox Code Playgroud)

但是,它真的很慢.我环顾四周,发现apply也可以做这项工作:

subindex = df['Date'].groupby(df.index).apply(lambda x: np.arange(1, len(x) + 1))
Run Code Online (Sandbox Code Playgroud)

当然subindex需要展平,因为它是一个列表列表.这比该transform方法快得多.然后我用for loop我自己的测试:

subindex_size = df.groupby(df.index, sort = False).size()
subindex = []
for i in np.arange(len(subindex_size)):
    subindex.extend(np.arange(1,subindex_size[i]+1))
Run Code Online (Sandbox Code Playgroud)

它甚至更快.使用我更大的数据集(大约90k行),该transform方法在我的计算机上大约需要44秒,apply需要大约2秒,并且for loop只需要大约1秒.我需要更大的数据集上工作,因此,即使是之间的时间差applyfor loop有差别给我.但是,for loop如果我需要创建其他基于组的变量,看起来很难看并且可能不容易应用.

所以我的问题是,为什么应该做正确的事情的内置函数更慢?我在这里遗漏了什么或者有什么理由吗?有没有其他方法可以改善这个过程?

And*_*den 6

您可以使用cumcount来执行此操作:

In [11]: df.groupby(level=0).cumcount()
Out[11]: 
ID
Bob    0
Bob    1
Tom    0
Tom    1
Tom    2
dtype: int64

In [12]: df['subindex'] = df.groupby(level=0).cumcount()  # possibly + 1 here.

In [13]: df.set_index('subindex', append=True)
Out[13]: 
               Content        Date
ID  subindex                      
Bob 0         birthday  2010.03.01
    1           school  2010.04.01
Tom 0         shopping  2010.02.01
    1             work  2010.09.01
    2          holiday  2010.10.01
Run Code Online (Sandbox Code Playgroud)

要从 1(而不是 0)开始,只需将 cumcount 的结果加 1。