看过来
《pandas 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
本需求是一个复杂的数据结构变化需求,可能会在数据特征获取中使用到。一般我们常见的数据分组是依列,比如以一个或者多个列作为分组依据,有时也会按行即列名进行分组,但本例将行和列同时作为分组依据,我们来看看 pandas 如何完成。
先构造源数据如下:
from io import StringIO
import pandas as pd
data = '''
g c1 c2
a 1 2
b 2 1
c 1 2
b 2 1
a 1 1
b 1 1
c 2 2
a 1 1
'''
df = pd.read_csv(StringIO(data), sep=r'\s+')
df
# ...
这个数据有三列,第一列 g 是一个分类型的数值,其他列的值取 1 或者 2,需求期望按照列来分组,来看看 g 中每个分类对应的 1 和 2 各有多少个,即得到以下结果。
'''
1 2
c1 a 3 0
b 1 2
c 1 1
c2 a 2 1
b 3 0
c 0 2
'''
接下来我们做一下思路分析。
在用 DataFrame 的 groupby() 分组时,axis 只能指定轴 0 或者轴 1,也就是行或者列,为了将源数据支持分组,我们要将列名也变形到行索引上,可以用堆叠的方式完成。
行列名都在行索引(多层索引)中后,按它们进行分组,对每组求相同值的数量,得到一个以每个值为标签,此值的数量为值的 Series,再解除堆叠将这个 Series 的索引标签转到列上,成为列名。
先将数据的 g 列设为索引,再进行堆叠操作,就得到了一个多层索引的 Series:
(
df.set_index('g')
.stack()
)
'''
g
a c1 1
c2 2
b c1 2
c2 1
c c1 1
c2 2
b c1 2
c2 1
a c1 1
c2 1
b c1 1
c2 1
c c1 2
c2 2
a c1 1
c2 1
dtype: int64
'''
原 DataFrame 所有的值会成为这个 Series 的值。接着进行分组,注意分组按索引时要使用 level 参数,根据需求,将多层索引的位置 1 的那层在前边,聚合方法使用 Series 的 value_counts() 来得到值的数量:
(
df.set_index('g')
.stack()
.groupby(level=[1, 0])
.apply(pd.Series.value_counts)
# .apply('value_counts') # 同上效果
)
'''
g
c1 a 1 3
b 2 2
1 1
c 1 1
2 1
c2 a 1 2
2 1
b 1 3
c 2 2
dtype: int64
'''
此时我们已经得到了所有想要的数据,接着再对数据进行变形,解堆:
(
df.set_index('g')
.stack()
.groupby(level=[1, 0])
.apply(pd.Series.value_counts)
.unstack()
)
'''
1 2
g
c1 a 3.0 NaN
b 1.0 2.0
c 1.0 1.0
c2 a 2.0 1.0
b 3.0 NaN
c NaN 2.0
'''
缺失值为对应分组没有的值,将它们填充为 0,再将所有值转为整型:
(
df.set_index('g')
.stack()
.groupby(level=[1, 0])
.apply(pd.Series.value_counts)
.unstack()
.fillna(0)
.astype(int)
)
'''
1 2
g
c1 a 3 0
b 1 2
c 1 1
c2 a 2 1
b 3 0
c 0 2
'''
这样就得到了最终的结果。
(完)
更新时间:2024-08-18 16:08:29 标签:pandas python 索引 分组