高阶 Pandas

本文介绍了Pandas的Categorical类型,用于高效处理分类数据,节省内存并提升性能。详细讲解了Categorical的创建、使用和相关计算方法,包括设置类别、去除未使用类别。此外,探讨了高阶GroupBy应用,如transform方法用于分组转换,TimeGrouper用于时间序列数据的重采样。通过实例展示了如何利用这些特性进行数据处理和分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 分类数据

  • 本节学习 pandas 的 Categorical 类型,学习在使用 pandas 进行某些操作时如何获取更好的性能和内存使用,另外还有一些在统计和机器学习中使用分类数据的工具

1.1 背景和目标

  • 一个列经常会包含重复值,这些重复值时一个小型的不同值的集合,unique 和 value_counts 函数允许我们从一个数组中提取不同值并分别计算这些不同值的频率
  • 为了提高性能,我们使用分类的方法(或者称为字典编码)来呈现数据,也就是说利用编码的方法,将数据以整数的方式呈现,相应的,存在一个类别(或者说字典),保存了数值所代表的含义
  • 如下例子,分类数据和类别均用 Series 表示,并且在类别上使用 take 方法,传入分类数据,即可还原原始数据
import numpy as np
import pandas as pd
def sep():
    print('*' * 20)
values = pd.Series(['apple', 'orange', 'apple', 'apple'] * 2)
print(values)
sep()
values = pd.Series([0, 1, 0, 0] * 2)
print(values)
sep()
dim = pd.Series(['apple', 'orange'])
print(dim)
sep()
print(dim.take(values).reset_index(drop=True))
0     apple
1    orange
2     apple
3     apple
4     apple
5    orange
6     apple
7     apple
dtype: object
********************
0    0
1    1
2    0
3    0
4    0
5    1
6    0
7    0
dtype: int64
********************
0     apple
1    orange
dtype: object
********************
0     apple
1    orange
2     apple
3     apple
4     apple
5    orange
6     apple
7     apple
dtype: object

1.2 pandas 中的 Categorical 类型

  • pandas 拥有特殊的 Categorical 类型,用于承载基于整数的类别展示或编码的数据,它有 categories 和 codes 两个属性

  • Categorical 类型的生成有如下三种方法

  • 使用 astype() 方法将 Series 的值转换为 Categorical 对象

fruits = ['apple', 'orange', 'apple', 'apple'] * 2
N = len(fruits)
df = pd.DataFrame({'fruit': fruits,
                   'basket_id': np.arange(N),
                  'count': np.random.randint(3, 15, size=N),
                  'weight': np.random.uniform(0, 4, size=N)})
fruit_cat = df['fruit'].astype('category')
print(fruit_cat)
sep()
c = fruit_cat.values
print(c)
sep()
print(type(c))
sep()
print(c.categories)
sep()
print(c.codes)
0     apple
1    orange
2     apple
3     apple
4     apple
5    orange
6     apple
7     apple
Name: fruit, dtype: category
Categories (2, object): [apple, orange]
********************
[apple, orange, apple, apple, apple, orange, apple, apple]
Categories (2, object): [apple, orange]
********************
<class 'pandas.core.arrays.categorical.Categorical'>
********************
Index(['apple', 'orange'], dtype='object')
********************
[0 1 0 0 0 1 0 0]
  • 从其他 Python 序列类型直接生成 pd.Categorical 对象
my_categories = pd.Categorical(['foo', 'bar', 'baz', 'foo', 'bar'])
print(my_categories)
[foo, bar, baz, foo, bar]
Categories (3, object): [bar, baz, foo]
  • 在已经获得分类编码数据的情况下使用 pd.Categorical.from_codes 方法
categories = ['foo', 'baz', 'bar']
codes = [0, 1, 2, 0, 0, 1]
my_cats_2 = pd.Categorical.from_codes(codes, categories)
print(my_cats_2)
[foo, baz, bar, foo, foo, baz]
Categories (3, object): [foo, baz, bar]
  • 默认情况下,分类转换没有指定类别的顺序,因此 categories 数组可能和输入的顺序不同,在使用 from_codes 或其他任意构造函数时,通过传递 ordered=True 来严格指定顺序
  • 一个未排序的分类实例可以使用 as_ordered() 方法进行排序
ordered_cat = pd.Categorical.from_codes(codes, categories, ordered=True)
print(ordered_cat)
sep()
print(my_cats_2.as_ordered())
[foo, baz, bar, foo, foo, baz]
Categories (3, object): [foo < baz < bar]
********************
[foo, baz, bar, foo, foo, baz]
Categories (3, object): [foo < baz < bar]

1.3 使用 Categorical 对象进行计算

  • 在 pandas 中使用 Categorical 与普通的非编码版本相比,整体上是一致的,不过 pandas 中的某些函数,例如 groupby,在与 Categorical 对象协同工作时性能更好,还有一些函数可以利用 ordered 标识
  • 如下例子,计算 1000 个随机数字的四分位分箱,并添加四分位数名称,然后再使用 groupby 提取一些汇总统计值
draws = np.random.randn(1000)
print(draws[:5])
sep()
bins = pd.qcut(draws, 4)
print(bins)
sep()
bins = pd.qcut(draws, 4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
print(bins)
sep()
print(bins.codes[:10])
sep()
bins = pd.Series(bins, name='quartile')
result = (pd.Series(draws).groupby(bins).agg(['count', 'min', 'max']).reset_index())
result
[-2.24878235  0.47012543 -0.333057    1.04582468 -0.94772562]
********************
[(-2.876, -0.682], (-0.029, 0.675], (-0.682, -0.029], (0.675, 3.349], (-2.876, -0.682], ..., (-0.029, 0.675], (-0.682, -0.029], (-0.029, 0.675], (-0.029, 0.675], (0.675, 3.349]]
Length: 1000
Categories (4, interval[float64]): [(-2.876, -0.682] < (-0.682, -0.029] < (-0.029, 0.675] < (0.675, 3.349]]
********************
[Q1, Q3, Q2, Q4, Q1, ..., Q3, Q2, Q3, Q3, Q4]
Length: 1000
Categories (4, object): [Q1 < Q2 < Q3 < Q4]
********************
[0 2 1 3 0 3 2 0 0 0]
********************
quartilecountminmax
0Q1250-2.875225-0.684647
1Q2250-0.681031-0.029240
2Q3250-0.0288500.674355
3Q42500.6756633.348693
  • 对于大量的数据集,使用分类能够获得更好的性能
  • 如下示例数据集包含 1000 万条数据,明显 categories 使用了更少的内存
N = 10000000
labels = pd.Series(['foo', 'bar', 'baz', 'qux'] * (N // 4))
categories = labels.astype('category')
print(labels.memory_usage())
print(categories.memory_usage())
%time _ = labels.astype('category')
80000080
10000272
Wall time: 294 ms

1.4 分类方法

  • Series 包含的分类数据拥有一些特殊的方法,这些方法提供了快捷访问类别和代码的方式
  • 如特殊属性 cat 提供了对分类方法的访问
s = pd.Series(['a', 'b', 'c', 'd'] * 2)
cat_s = s.astype('category')
print(cat_s)
sep()
print(cat_s.cat.codes)
sep()
print(cat_s.cat.categories)
0    a
1    b
2    c
3    d
4    a
5    b
6    c
7    d
dtype: category
Categories (4, object): [a, b, c, d]
********************
0    0
1    1
2    2
3    3
4    0
5    1
6    2
7    3
dtype: int8
********************
Index(['a', 'b', 'c', 'd'], dtype='object')
  • cat.set_categories() 方法可以改变类别
actual_categories = ['a', 'b', 'c', 'd', 'e']
cat_s2 = cat_s.cat.set_categories(actual_categories)
print(cat_s2)
sep()
print(cat_s2.value_counts())
0    a
1    b
2    c
3    d
4    a
5    b
6    c
7    d
dtype: category
Categories (5, object): [a, b, c, d, e]
********************
d    2
c    2
b    2
a    2
e    0
dtype: int64
  • cat.remove_unused_categories 方法可以去除未观察到的类别
cat_s3 = cat_s[cat_s.isin(['a', 'b'])]
print(cat_s3)
sep()
print(cat_s3.cat.remove_unused_categories())
0    a
1    b
4    a
5    b
dtype: category
Categories (4, object): [a, b, c, d]
********************
0    a
1    b
4    a
5    b
dtype: category
Categories (2, object): [a, b]
  • pandas 中 Series 的分类方法
方法描述
add_categories将新的类别(未使用过的)添加到已有类别的尾部
as_ordered对类别排序
as_unordered使类别无序
remove_categories去除类别,将被移除的值置为 null
remove_unused_categories去除所有没有出现在数据中的类别
rename_categories使用新的类别名称替代现有的类别,不会改变类别的数量
reorder_categories与 rename_categories 类似,但结果是经过排序的类别
set_categories用指定的一组新类别替换现有类别,可以添加或删除类别
  • 在使用统计数据或机器学习工具时,通常会将分类数据转换为虚拟变量,也称为 one-hot 编码
  • pandas.get_dummies() 函数将一维的分类数据转换为一个包含虚拟变量的 DataFrame,每个不同的类别都是它的一列,这些列包含特定类别的出现次数,否则为 0
pd.get_dummies(cat_s)
abcd
01000
10100
20010
30001
41000
50100
60010
70001

2. 高阶 GroupBy 应用

2.1 分组转换和 “展开” GroupBy

  • groupby 除了使用 apply 方法执行转换操作外,还有一个内建方法 transform,它与 apply 类似但是对可以使用的函数种类有更多的限制
    • transform 可以产生一个标量值,并广播到各分组的尺寸数据中
    • transform 可以产生一个与输入分组尺寸相同的对象
    • transform 不可改变它的输入
df = pd.DataFrame({'key': ['a', 'b', 'c'] * 4,
                  'value': np.arange(12.)})
df
keyvalue
0a0.0
1b1.0
2c2.0
3a3.0
4b4.0
5c5.0
6a6.0
7b7.0
8c8.0
9a9.0
10b10.0
11c11.0
  • 按 key 分类后 value 的均值
g = df.groupby('key').value
print(g.mean())
key
a    4.5
b    5.5
c    6.5
Name: value, dtype: float64
  • 使用 transform 方法,并传入匿名函数或字符串别名(仅针对内建的聚合函数),可以得到一个尺寸和 df[‘value’] 一样,但值都被按‘key’分组的均值替代的 Series
  • 也就是前面说的 transform 内的函数如果返回的是标量值,则会广播到各分组中
print(g.transform(lambda x: x.mean()))
sep()
print(g.transform('mean'))
0     4.5
1     5.5
2     6.5
3     4.5
4     5.5
5     6.5
6     4.5
7     5.5
8     6.5
9     4.5
10    5.5
11    6.5
Name: value, dtype: float64
********************
0     4.5
1     5.5
2     6.5
3     4.5
4     5.5
5     6.5
6     4.5
7     5.5
8     6.5
9     4.5
10    5.5
11    6.5
Name: value, dtype: float64
  • 与 apply 类似,transform 可以与返回 series 的函数一起使用,但结果必须和输入有相同的大小
print(g.transform(lambda x: x * 2))
sep()
print(g.transform(lambda x: x.rank(ascending=False)))    # 按照每个组的降序计算排名
0      0.0
1      2.0
2      4.0
3      6.0
4      8.0
5     10.0
6     12.0
7     14.0
8     16.0
9     18.0
10    20.0
11    22.0
Name: value, dtype: float64
********************
0     4.0
1     4.0
2     4.0
3     3.0
4     3.0
5     3.0
6     2.0
7     2.0
8     2.0
9     1.0
10    1.0
11    1.0
Name: value, dtype: float64

2.2 分组的时间重新采样

  • 对于时间序列数据,resample 方法在语义上是一种基于时间分段的分组操作,如下示例,对 df 进行 5min 重采样
N = 15
times = pd.date_range('2017-05-20 00:00', freq='1min', periods=N)
df = pd.DataFrame({'time': times,
                  'value': np.arange(N)})
print(df)
sep()
print(df.set_index('time').resample('5min').count())
                  time  value
0  2017-05-20 00:00:00      0
1  2017-05-20 00:01:00      1
2  2017-05-20 00:02:00      2
3  2017-05-20 00:03:00      3
4  2017-05-20 00:04:00      4
5  2017-05-20 00:05:00      5
6  2017-05-20 00:06:00      6
7  2017-05-20 00:07:00      7
8  2017-05-20 00:08:00      8
9  2017-05-20 00:09:00      9
10 2017-05-20 00:10:00     10
11 2017-05-20 00:11:00     11
12 2017-05-20 00:12:00     12
13 2017-05-20 00:13:00     13
14 2017-05-20 00:14:00     14
********************
                     value
time                      
2017-05-20 00:00:00      5
2017-05-20 00:05:00      5
2017-05-20 00:10:00      5
  • 对于一个包含多个时间序列,并且每个时间序列都使用一个附加的分组键进行标记的 DataFrame,要为每个时间序列进行相同的 5min 重采样,可以使用 pandas.TimeGrouper 对象,该对象可以被用作分组键
  • 使用 pandas.TimeGrouper 的一个限制是时间必须是 Series 或 DataFrame 的索引
  • 官方提示 TimeGrouper 未来会被取消掉,使用 pd.Grouper(freq=…)替代
df2 = pd.DataFrame({'time': times.repeat(3),
                   'key': np.tile(['a', 'b', 'c'], N),
                   'value': np.arange(N * 3.)})
print(df2[:10])
sep()
time_key = pd.TimeGrouper('5min')
resampled = (df2.set_index('time').groupby(['key', time_key])).sum()
print(resampled)
sep()
print(resampled.reset_index())
                 time key  value
0 2017-05-20 00:00:00   a    0.0
1 2017-05-20 00:00:00   b    1.0
2 2017-05-20 00:00:00   c    2.0
3 2017-05-20 00:01:00   a    3.0
4 2017-05-20 00:01:00   b    4.0
5 2017-05-20 00:01:00   c    5.0
6 2017-05-20 00:02:00   a    6.0
7 2017-05-20 00:02:00   b    7.0
8 2017-05-20 00:02:00   c    8.0
9 2017-05-20 00:03:00   a    9.0
********************
                         value
key time                      
a   2017-05-20 00:00:00   30.0
    2017-05-20 00:05:00  105.0
    2017-05-20 00:10:00  180.0
b   2017-05-20 00:00:00   35.0
    2017-05-20 00:05:00  110.0
    2017-05-20 00:10:00  185.0
c   2017-05-20 00:00:00   40.0
    2017-05-20 00:05:00  115.0
    2017-05-20 00:10:00  190.0
********************
  key                time  value
0   a 2017-05-20 00:00:00   30.0
1   a 2017-05-20 00:05:00  105.0
2   a 2017-05-20 00:10:00  180.0
3   b 2017-05-20 00:00:00   35.0
4   b 2017-05-20 00:05:00  110.0
5   b 2017-05-20 00:10:00  185.0
6   c 2017-05-20 00:00:00   40.0
7   c 2017-05-20 00:05:00  115.0
8   c 2017-05-20 00:10:00  190.0


d:\python 3.7\lib\site-packages\ipykernel_launcher.py:6: FutureWarning: pd.TimeGrouper is deprecated and will be removed; Please use pd.Grouper(freq=...)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值