《利用python进行数据分析》第二版 第12章-高阶pandas 学习笔记

一、分类数据

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

背景和目标

  • 一个列经常会包含重复值,这些重复值是一个小型的不同值的集合,unique 和 value_counts 函数可从一个数组中提取不同值并分别计算这些不同值的频率
  • 为了提高性能,用分类的方法(或者称为字典编码)来呈现数据,即用编码的方法将数据以整数的方式呈现;同时,存在一个类别(或字典)保存了数值所代表的含义
import numpy as np
import pandas as pd

values = pd.Series(['apple', 'orange', 'apple', 'apple'] * 2)
values
'''
0     apple
1    orange
2     apple
3     apple
4     apple
5    orange
6     apple
7     apple
dtype: object
'''

# 查看values列有哪些不同的值
pd.unique(values)
'''array(['apple', 'orange'], dtype=object)'''

# 为values列不同的值计数
pd.value_counts(values)

'''
apple     6
orange    2
dtype: int64
'''
# 用重复的值表示数据
# 将主要观测值存储为引用维度表的整数键
values = pd.Series([0, 1, 0, 0] * 2)
values
# 数据的类别、字典/层级
'''
0    0
1    1
2    0
3    0
4    0
5    1
6    0
7    0
dtype: int64
'''

dim = pd.Series(['apple', 'orange'])
dim
'''
0     apple
1    orange
dtype: object
'''

# 分类数据和类别均为Series,在类别上用take方法,传入分类数据,即可还原原始数据
# 用take方法恢复原来的字符串Series
dim.take(values)
'''
0     apple
1    orange
0     apple
0     apple
0     apple
1    orange
0     apple
0     apple
dtype: object
'''

pandas 中的 Categorical 类型

pandas 拥有特殊的 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)},
                  columns=['basket_id', 'fruit', 'count', 'weight'])
df
basket_idfruitcountweight
00apple53.858058
11orange82.612708
22apple42.995627
33apple72.614279
44apple122.990859
55orange83.845227
66apple50.033553
77apple40.425778
# 用astype('category')将fruit水果列数据类型dtype设置为category,原来为object
fruit_cat = df['fruit'].astype('category')
fruit_cat
'''
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']
'''

# 得到的为pandas.Categorical实例
c = fruit_cat.values
type(c)
'''pandas.core.arrays.categorical.Categorical'''

# 参看Categorical对象的类别Categorical.categories
c.categories
'''Index(['apple', 'orange'], dtype='object')'''

c.codes
'''array([0, 1, 0, 0, 0, 1, 0, 0], dtype=int8)'''
# 更改原DataFrmae中fruit列的类别为Categorical
df['fruit'] = df['fruit'].astype('category')
df.fruit
'''
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']
'''

用pd.Categorical()直接从其他python序列生成Categorical对象

# 用pd.Categorical()直接从其他python序列生成Categorical对象
my_categories = pd.Categorical(['foo', 'bar', 'baz', 'foo', 'bar'])
my_categories
'''
['foo', 'bar', 'baz', 'foo', 'bar']
Categories (3, object): ['bar', 'baz', 'foo']
'''

在已经获得分类编码数据时,用pd.Categorical.from_codes()构造函数

# 在已经获得分类编码数据时,用pd.Categorical.from_codes()构造函数
categories = ['foo', 'bar', 'baz']
codes = [0, 1, 2, 0, 0, 1]
my_cats_2 = pd.Categorical.from_codes(codes, categories)
my_cats_2
'''
['foo', 'bar', 'baz', 'foo', 'foo', 'bar']
Categories (3, object): ['foo', 'bar', 'baz']
'''
  • 默认情况下,分类转换没有指定类别的顺序,因此 categories 数组可能和输入的顺序不同,在使用 from_codes 或其他任意构造函数时,通过传递 ordered=True 来严格指定顺序
  • 一个未排序的分类实例可用 as_ordered() 方法进行排序
ordered_cat = pd.Categorical.from_codes(codes, categories,
                                        ordered=True)
ordered_cat
'''
['foo', 'bar', 'baz', 'foo', 'foo', 'bar']
Categories (3, object): ['foo' < 'bar' < 'baz']
'''

# 对未排序的分类实例使用as_ordered()排序
my_cats_2.as_ordered()
'''
['foo', 'bar', 'baz', 'foo', 'foo', 'bar']
Categories (3, object): ['foo' < 'bar' < 'baz']
'''

使用 Categorical 对象进行计算

np.random.seed(12345)
draws = np.random.randn(1000)
draws[:5]
'''array([-0.2047,  0.4789, -0.5194, -0.5557,  1.9658])'''

# 计算1000个随机数字的四分位分箱
bins = pd.qcut(draws, 4)
bins
'''
[(-0.684, -0.0101], (-0.0101, 0.63], (-0.684, -0.0101], (-0.684, -0.0101], (0.63, 3.928], ..., (-0.0101, 0.63], (-0.684, -0.0101], (-2.9499999999999997, -0.684], (-0.0101, 0.63], (0.63, 3.928]]
Length: 1000
Categories (4, interval[float64]): [(-2.9499999999999997, -0.684] < (-0.684, -0.0101] < (-0.0101, 0.63] < (0.63, 3.928]]
'''

# 使用label添加四分位数名称
bins = pd.qcut(draws, 4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
bins
'''
['Q2', 'Q3', 'Q2', 'Q2', 'Q4', ..., 'Q3', 'Q2', 'Q1', 'Q3', 'Q4']
Length: 1000
Categories (4, object): ['Q1' < 'Q2' < 'Q3' < 'Q4']
'''

bins.codes[:10]
'''array([1, 2, 1, 1, 3, 3, 2, 2, 3, 3], dtype=int8)'''
# 用 groupby 提取汇总统计值
bins = pd.Series(bins, name='quartile')
results = (pd.Series(draws).groupby(bins).agg(['count', 'min', 'max']).reset_index())
results
quartilecountminmax
0Q1250-2.949343-0.685484
1Q2250-0.683066-0.010115
2Q3250-0.0100320.628894
3Q42500.6342383.927528
# 结果中'quartile'列保留了bins中原始的分类信息,包括顺序
results['quartile']
'''
0    Q1
1    Q2
2    Q3
3    Q4
Name: quartile, dtype: category
Categories (4, object): ['Q1' < 'Q2' < 'Q3' < 'Q4']
'''

数据量比较大时,使用分类能够获得更好的性能

N = 10000000
draws = pd.Series(np.random.randn(N))
labels = pd.Series(['foo', 'bar', 'baz', 'qux'] * (N // 4))

categories = labels.astype('category')

# dtype: objects占用的内存
labels.memory_usage()
'''80000128'''

# dtype: category占用的内存
categories.memory_usage()
'''10000320'''

# 分类转换耗时
%time _ = labels.astype('category')
'''Wall time: 988 ms'''

分类方法

pandas 中 Series 的分类方法:涉及对Categorical 类型的增删改及重命名、排序

方法(Series.cat.)描述
add_categories将新的类别(未使用过的)添加到已有类别的尾部
as_ordered对类别排序
as_unordered使类别无序
remove_categories去除类别,将被移除的值置为 null
remove_unused_categories去除所有没有出现在数据中的类别
rename_categories使用新的类别名称替代现有的类别,不会改变类别的数量
reorder_categories与 rename_categories 类似,但结果是经过排序的类别
set_categories用指定的一组新类别替换现有类别,可以添加或删除类别
s = pd.Series(['a', 'b', 'c', 'd'] * 2)
cat_s = s.astype('category')
cat_s
'''
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']
'''

cat_s.cat.codes
'''
0    0
1    1
2    2
3    3
4    0
5    1
6    2
7    3
dtype: int8
'''

# 用属性categories查看有哪些分类
cat_s.cat.categories
'''
Index(['a', 'b', 'c', 'd'], dtype='object')
'''

# 用set_categories()重新设置分类
actual_categories = ['a', 'b', 'c', 'd', 'e']
cat_s2 = cat_s.cat.set_categories(actual_categories)
cat_s2
'''
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']
'''

# 以上结果看起来并未改变,雇佣value_counts()验证

cat_s.value_counts()
'''
d    2
c    2
b    2
a    2
dtype: int64
'''

cat_s2.value_counts()
'''
d    2
c    2
b    2
a    2
e    0
dtype: int64
'''

# 对于cat_s3类别c、d并没有出现在结果中
# 可用remove_unused_categories()去除未使用的类别
cat_s3 = cat_s[cat_s.isin(['a', 'b'])]
cat_s3
'''
0    a
1    b
4    a
5    b
dtype: category
Categories (4, object): ['a', 'b', 'c', 'd']
'''

cat_s3.cat.remove_unused_categories()
'''
0    a
1    b
4    a
5    b
dtype: category
Categories (2, object): ['a', 'b']
'''

创建用于建模的虚拟变量

  • 在用统计数据或机器学习工具时,通常会将分类数据转换为虚拟变量,也称为 one-hot 编码
  • pandas.get_dummies() 函数将一维的分类数据转换为一个包含虚拟变量的 DataFrame,每个不同的类别都是它的一列,这些列包含特定类别的出现次数,否则为 0
cat_s
'''
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']
'''

cat_s = pd.Series(['a', 'b', 'c', 'd'] * 2, dtype='category')
pd.get_dummies(cat_s)
abcd
01000
10100
20010
30001
41000
50100
60010
70001

二、高阶GroupBy应用

分组转换和 “展开” GroupBy

GroupBy的transform()方法,与apply 类似;但对可以使用的函数种类有更多的限制:

  • transform 可以产生一个标量值,并广播到各分组的尺寸数据中,即结果的shape与原shape相同
  • 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
g.mean()
'''key
a    4.5
b    5.5
c    6.5
Name: value, dtype: float64'''

# 用transform(),传入匿名函数,
# 可得到一个尺寸和df[‘value’]一样,但值都被按‘key’分组的均值替代的 Series
# 即transform()返回标量值会分别广播道对应的组
g.transform(lambda x: x.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
'''

# 用transform(),传入字符串别名(仅针对内建的聚合函数),
# 可得到一个尺寸和df[‘value’]一样,但值都被按‘key’分组的均值替代的 Series
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
'''

# 与apply类似,transform可与返回series的函数一起使用,但结果必须和输入有相同的大小
g.transform(lambda x: x * 2)
'''
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
'''

g.transform(lambda x: x.rank(ascending=False))
'''
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
'''

# 有简单聚合构成的分组转换函数传递给transform()/apply()结果等价
def normalize(x):
    return (x - x.mean()) / x.std()

g.apply(normalize)
'''
0    -1.161895
1    -1.161895
2    -1.161895
3    -0.387298
4    -0.387298
5    -0.387298
6     0.387298
7     0.387298
8     0.387298
9     1.161895
10    1.161895
11    1.161895
Name: value, dtype: float64
'''

# 但与apply()相比,transform()更快
g.transform(normalize)
'''
0    -1.161895
1    -1.161895
2    -1.161895
3    -0.387298
4    -0.387298
5    -0.387298
6     0.387298
7     0.387298
8     0.387298
9     1.161895
10    1.161895
11    1.161895
Name: value, dtype: float64
'''

normalized = (df['value'] - g.transform('mean')) / g.transform('std')
normalized
'''
0    -1.161895
1    -1.161895
2    -1.161895
3    -0.387298
4    -0.387298
5    -0.387298
6     0.387298
7     0.387298
8     0.387298
9     1.161895
10    1.161895
11    1.161895
Name: value, dtype: float64
'''

分组的时间重新采样

对时间序列数据,resample 方法在语义上是一种基于时间分段的分组操作。

N = 15
times = pd.date_range('2017-05-20 00:00', freq='1min', periods=N)
df = pd.DataFrame({'time': times,
                   'value': np.arange(N)})
df
timevalue
02017-05-20 00:00:000
12017-05-20 00:01:001
22017-05-20 00:02:002
32017-05-20 00:03:003
42017-05-20 00:04:004
52017-05-20 00:05:005
62017-05-20 00:06:006
72017-05-20 00:07:007
82017-05-20 00:08:008
92017-05-20 00:09:009
102017-05-20 00:10:0010
112017-05-20 00:11:0011
122017-05-20 00:12:0012
132017-05-20 00:13:0013
142017-05-20 00:14:0014
# 对df,按'time'进行索引,再进行5min重采样
df.set_index('time').resample('5min').count()
value
time
2017-05-20 00:00:005
2017-05-20 00:05:005
2017-05-20 00:10:005
# 同时包含'time'与分组键'key'
df2 = pd.DataFrame({'time': times.repeat(3),
                    'key': np.tile(['a', 'b', 'c'], N),
                    'value': np.arange(N * 3.)})
df2[:7]
timekeyvalue
02017-05-20 00:00:00a0.0
12017-05-20 00:00:00b1.0
22017-05-20 00:00:00c2.0
32017-05-20 00:01:00a3.0
42017-05-20 00:01:00b4.0
52017-05-20 00:01:00c5.0
62017-05-20 00:02:00a6.0
# pd.Grouper()对每个'key'值进行相同的重新采样,传递freq='5min'
# pd.Grouper()限制时间序列必须是Series或DataFrame
time_key = pd.Grouper(freq='5min')

resampled = (df2.set_index('time').groupby(['key', time_key]).sum())
resampled
value
keytime
a2017-05-20 00:00:0030.0
2017-05-20 00:05:00105.0
2017-05-20 00:10:00180.0
b2017-05-20 00:00:0035.0
2017-05-20 00:05:00110.0
2017-05-20 00:10:00185.0
c2017-05-20 00:00:0040.0
2017-05-20 00:05:00115.0
2017-05-20 00:10:00190.0
resampled.reset_index()
keytimevalue
0a2017-05-20 00:00:0030.0
1a2017-05-20 00:05:00105.0
2a2017-05-20 00:10:00180.0
3b2017-05-20 00:00:0035.0
4b2017-05-20 00:05:00110.0
5b2017-05-20 00:10:00185.0
6c2017-05-20 00:00:0040.0
7c2017-05-20 00:05:00115.0
8c2017-05-20 00:10:00190.0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值