pandas:如何在多级索引中运行透视表?

67

我想在 pandas 的 DataFrame 上运行透视(pivot),其中索引包括两个列,而不是一个。例如,一个字段表示年份,一个字段表示月份,一个“item”字段显示“item 1”和“item 2”,以及一个数字值的“value”字段。我希望索引是年份 + 月份。

我唯一设法让它起作用的方法是将这两个字段合并为一个,然后再分离它们。有更好的方法吗?

以下是代码示例。非常感谢!

PS 是的,我知道还有其他与关键字“pivot”和“multi-index”相关的问题,但我不明白它们如何可以帮助我解决这个问题。

import pandas as pd
import numpy as np

df= pd.DataFrame()
month = np.arange(1, 13)
values1 = np.random.randint(0, 100, 12)
values2 = np.random.randint(200, 300, 12)


df['month'] = np.hstack((month, month))
df['year'] = 2004
df['value'] = np.hstack((values1, values2))
df['item'] = np.hstack((np.repeat('item 1', 12), np.repeat('item 2', 12)))

# This doesn't work: 
# ValueError: Wrong number of items passed 24, placement implies 2
# mypiv = df.pivot(['year', 'month'], 'item', 'value')

# This doesn't work, either:
# df.set_index(['year', 'month'], inplace=True)
# ValueError: cannot label index with a null key
# mypiv = df.pivot(columns='item', values='value')

# This below works but is not ideal: 
# I have to first concatenate then separate the fields I need
df['new field'] = df['year'] * 100 + df['month']

mypiv = df.pivot('new field', 'item', 'value').reset_index()
mypiv['year'] = mypiv['new field'].apply( lambda x: int(x) / 100)  
mypiv['month'] = mypiv['new field'] % 100

3
我已在此 Q&A 中提供了几个详细的示例和替代方法。 - piRSquared
4个回答

115

你可以进行分组,然后取消分组。

>>> df.groupby(['year', 'month', 'item'])['value'].sum().unstack('item')
item        item 1  item 2
year month                
2004 1          33     250
     2          44     224
     3          41     268
     4          29     232
     5          57     252
     6          61     255
     7          28     254
     8          15     229
     9          29     258
     10         49     207
     11         36     254
     12         23     209

或者使用pivot_table

>>> df.pivot_table(
        values='value', 
        index=['year', 'month'], 
        columns='item', 
        aggfunc=np.sum)
item        item 1  item 2
year month                
2004 1          33     250
     2          44     224
     3          41     268
     4          29     232
     5          57     252
     6          61     255
     7          28     254
     8          15     229
     9          29     258
     10         49     207
     11         36     254
     12         23     209

3
@Alexander, pivot_table()函数需要提供_aggfunc_参数。如果没有提供此参数,则默认使用mean()函数。如果需要使用sum()功能,则在调用pivot_table()函数时应该添加aggfunc=sum参数。 - rp1
@rp1 很好的观点。这是因为原始样本数据框每个项目、年份和月份只有一个值。修改响应以反映更正。 - Alexander

34

我相信如果你在你的MultiIndex中包含item,那么你可以直接使用unstack:

df.set_index(['year', 'month', 'item']).unstack(level=-1)

这将产生:

                value      
item       item 1 item 2
year month              
2004 1         21    277
     2         43    244
     3         12    262
     4         80    201
     5         22    287
     6         52    284
     7         90    249
     8         14    229
     9         52    205
     10        76    207
     11        88    259
     12        90    200

它比使用pivot_table要快一点,速度与使用groupby相当或稍微慢一些。


1
您还可以直接引用索引级别,例如 df.set_index(['year', 'month', 'item']).unstack('fcode')。在更广义的问题(而非本题)中,Alexander在他的回答中提到的第一种方法也会因为非数值型数据而失败。 - Carl
这是一个惊人的答案。 - Jason Wolosonovich
我喜欢这个答案,但是在实现时遇到了一些麻烦。我有多列数据,希望将它们展平成单行。这个答案让我接近目标,但是我的数据中出现了很多NaN。你有什么想法吗? - Adrian Keister
没关系,我已经解决了。我使用了这样的代码:df = pd.DataFrame(df.set_index(['PRS 3']).unstack(-1)) 然后 df = df.transpose() - Adrian Keister

4
以下对我有效:
mypiv = df.pivot(index=['year','month'],columns='item')[['values1','values2']]

0
感谢 gmoutso comment,您可以使用以下内容:
def multiindex_pivot(df, index=None, columns=None, values=None):
    if index is None:
        names = list(df.index.names)
        df = df.reset_index()
    else:
        names = index
    list_index = df[names].values
    tuples_index = [tuple(i) for i in list_index] # hashable
    df = df.assign(tuples_index=tuples_index)
    df = df.pivot(index="tuples_index", columns=columns, values=values)
    tuples_index = df.index  # reduced
    index = pd.MultiIndex.from_tuples(tuples_index, names=names)
    df.index = index
    return df

用法:

df.pipe(multiindex_pivot, index=['idx_column1', 'idx_column2'], columns='foo', values='bar')

如果您想要一个简单的扁平列结构,并且希望列具有其预期的类型,请添加以下内容:

(df
   .infer_objects()  # coerce to the intended column type
   .rename_axis(None, axis=1))  # flatten column headers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接