目录
前言
本系列共有三篇文章,依次按照pandas数据类型及其结构、内置模块对数据处理功能、可视化工具以及形如房价预测的案例分析内容展开介绍。参考自书籍《Python for Data Analysis(Second Edition)》,本篇文章的代码均已测试通过,数据集下载详见【资源】。
📢注意:代码文件应和解压后的数据及文件夹在同一目录下才能相对路径引用到,当然也可使用绝对路径。
六、数据规整:连接、联合与重塑
在jupyter notebook中写项目时,应当形成先将所需的包导入、设置可视化的基础选项、随机初始化等习惯。因为Python解释器的运行程序方式与C环境(预编译后就可以执行)不同,所以在下一次cell中需要再次运行在其他cell中定义的模块。
以下是本节中所需的预操作:
import numpy as np
import pandas as pd
pd.options.display.max_rows = 20
np.random.seed(12345)
import matplotlib.pyplot as plt
plt.rc('figure', figsize=(10, 6))
np.set_printoptions(precision=4, suppress=True)
1.分层索引
data = pd.Series(np.random.randn(9),
index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'],
[1, 2, 3, 1, 3, 1, 2, 2, 3]])
print(data, '\n')
print(data.index, '\n')
# 分层索引/部分索引
data['b':'c'] # 标签b到c
data.loc[['b', 'd']] # 标签b和d
# 重塑数据
print(data.unstack(), '\n') # 反操作为data.unstack().stack()
frame = pd.DataFrame(np.arange(12).reshape((4, 3)),
index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
columns=[['Ohio', 'Ohio', 'Colorado'],
['Green', 'Red', 'Green']])
print(frame, '\n')
# 给层级命名:
frame.index.names = ['key1', 'key2']
frame.columns.names = ['state', 'color']
print(frame, '\n')
# 分层索引
frame['Ohio']
①重排序和层级排序
swaplevel()
sort_index()
# 接受两个层级序号或名称,返回一个进行层级互换的新对象(对应层级下的数据不变)
frame.swaplevel('key1', 'key2')
# 在单一层级上对数据进行排序
print(frame.sort_index(level=1), '\n')
frame.swaplevel(0, 1).sort_index(level=0)
②按层级进行汇总统计
frame.sum()
# 2023年,官方不建议书籍中的写法(2017年的写法),运行后报警告提示未来可能会淘汰这样的写法:frame.sum(level='key2')
print(frame.groupby(level='key2').sum(), '\n')
frame.groupby(level='color', axis=1).sum()
③使用DataFrame的列进行索引
frame = pd.DataFrame({'a': range(7), 'b': range(7, 0, -1),
'c': ['one', 'one', 'one', 'two', 'two',
'two', 'two'],
'd': [0, 1, 2, 0, 1, 2, 3]})
print(frame, '\n')
# 使用一个或多个列作为索引,默认情况下这些列会从DataFrame中移除(drop=False),也可以设置其不移除
frame2 = frame.set_index(['c', 'd'], drop=True)
print(frame2, '\n')
# 反操作
frame2.reset_index()
2.联合与合并数据集
pandas.merge
根据一个或多个键将行进行连接pandas.concat
使对象在轴向上进行粘合或“堆叠”combine_first
实例方法允许将重叠的数据拼接在一起,以用一个对象中的值填充另一个对象中的缺失值
①数据库风格的DataFrame连接
merge()
合并或连接操作通过一个或多个键连接行来联合数据集。
df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
'data1': range(7)})
df2 = pd.DataFrame({'key': ['a', 'b', 'd'],
'data2': range(3)})
print('df1如下:\n', df1, '\n')
print('df2如下:\n', df2, '\n')
pd.merge()
参数如下:
参数 | 描述 |
---|---|
left | 合并时操作中左边的DataFrame |
right | 合并时操作中右边的DataFrame |
how | inner 、outer 、left 、right 之一;默认是inner |
on | 需要连接的列名,必须是在两边的DataFrame对象都有的列名,并以left和right中的列名的交集作为连接键 |
left_on | left DataFrame中用作连接键的列 |
right_on | right DataFrame中用作连接键的列 |
sort | 通过连接键按字母顺序对合并的数据进行排序;在默认情况下为True(在大数据集上某些情况下禁用该功能可以获得更好的性能) |
suffixes | 在重叠的情况下,添加到列名后的字符串元组;默认是('_x', '_y') (例如如果待合并的DataFrame中都有’data’列,那么结果中会出现 data_x ,data_y ) |
copy | 如果为False,则在某些特殊情况下避免将数据复制到结果数据结构中;默认情况下总是复制 |
indicator | 添加一个特殊的列 _merge ,指示每一行的来源;值将根据每行中连接数据的来源分别为left_only ,right_only 或both |
# 如果没有指定在哪一列上进行连接,merge会自动将重叠列名作为连接的键
print(pd.merge(df1, df2), '\n')
print(pd.merge(df1, df2, on='key'), '\n') # 指定连接的键
# merge默认情况下是内连接,结果的键是两张表的交集,而要将两张表中仅一方有的键都放到结果中,需要外连接(即并集)
print(pd.merge(df1, df2, how='outer'), '\n')
# 为两个对象指定连接后的列名
df3 = pd.DataFrame({'lkey': ['b', 'b', 'a', 'c', 'a', 'b'],
'data1': range(6)})
df4 = pd.DataFrame({'rkey': ['a', 'b', 'a', 'b', 'd'],
'data2': range(5)})
print(pd.merge(df3, df4, left_on='lkey', right_on='rkey'), '\n')
pd.merge(how=paraments)
中how参数列表:
选项 | 行为 |
---|---|
inner | 只对两张表都有的键的交集进行联合 |
left | 对所有左表的键进行联合 |
right | 对所有右表的键进行联合 |
outer | 对两张表都有的键的并集进行联合 |
df3 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'b'],
'data1': range(6)})
df4 = pd.DataFrame({'key': ['a', 'b', 'a', 'b', 'd'],
'data2': range(5)})
# 对所有左表的键进行联合
print(pd.merge(df3, df4, on='key', how='left'), '\n')
# 多对多连接是行的笛卡尔积,左表有3个b,右表有2个b,根据笛卡尔积连接后共有6个b
print(pd.merge(df1, df2, how='inner'), '\n')
left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'],
'key2': ['one', 'two', 'one'],
'lval': [1, 2, 3]})
right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],
'key2': ['one', 'one', 'one', 'two'],
'rval': [4, 5, 6, 7]})
# 使用多个键合并时,传入一个列名的列表
print(pd.merge(left, right, on=['key1', 'key2'], how='outer'), '\n')
# 处理重叠的列名
print(pd.merge(left, right, on='key1'), '\n') # 区别左右表默认添加 _x、_y
pd.merge(left, right, on='key1', suffixes=('_left', '_right'))
②根据索引合并(Merging on Index)
传递参数 left_index=True
或 right_index=True
left1 = pd.DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'],
'value': range(6)})
right1 = pd.DataFrame({'group_val': [3.5, 7]}, index=['a', 'b'])
print('left1:\n', left1, '\n')
print('right1:\n', right1, '\n')
# 默认的合并方法是连接键相交,也可以使用 outer 来取其并集
pd.merge(left1, right1, left_on='key', right_index=True)
lefth = pd.DataFrame({'key1': ['Ohio', 'Ohio', 'Ohio',
'Nevada', 'Nevada'],
'key2': [2000, 2001, 2002, 2001, 2002],
'data': np.arange(5.)})
righth = pd.DataFrame(np.arange(12).reshape((6, 2)),
index=[['Nevada', 'Nevada', 'Ohio', 'Ohio',
'Ohio', 'Ohio'],
[2001, 2000, 2000, 2000, 2001, 2002]],
columns=['event1', 'event2'])
print(lefth, '\n')
print(righth, '\n')
# 此时必须以列表的形式指明合并所需的多列
print(pd.merge(lefth, righth, left_on=['key1', 'key2'], right_index=True), '\n')
merge_lists = pd.merge(lefth, righth, left_on=['key1', 'key2'],
right_index=True, how='outer') # 使用 outer 来取重叠列的并集
print(merge_lists, '\n')
left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
index=['a', 'c', 'e'],
columns=['Ohio', 'Nevada'])
right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
index=['b', 'c', 'd', 'e'],
columns=['Missouri', 'Alabama'])
left2
right2
pd.merge(left2, right2, how='outer', left_index=True, right_index=True)
# 用以合并多个索引相同或相似但没有重叠列的DataFrame对象
left2.join(right2, how='outer')
# 对于简单索引-索引合并,可以向join方法传入一个DataFrame列表,这个方法可以替代 concat 函数的方法
another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],
index=['a', 'c', 'e', 'f'],
columns=['New York', 'Oregon'])
another
left2.join([right2, another])
left2.join([right2, another], how='outer')
③连轴向连接
concatence()
参数如下:
参数 | 描述 |
---|---|
objs | 需要连接的pandas对象列表或字典,这是必选参数 |
axis | 连接的轴向,默认是0(沿着行方向) |
join | 可以是'inner' 或'outer' (默认是'outer' ),用于指定连接的方式 |
join_axes | 用于指定其他n-1轴的特定索引,可以替代内/外连接的逻辑 |
keys | 与要连接的对象关联的值,沿着连接轴形成分层索引;可以是任意值的列表或数组,或元组的数组,或数组的列表(如果向levels参数传入多层数组) |
levels | 在键值传递时,该参数用于指定多层索引的层级 |
names | 如果传入了keys和/或levels参数,该参数用于多层索引的层级名称 |
verify_integrity | 检查连接对象中的新轴是否重复,如果是,则引发异常;默认(False)允许重复 |
ignore_index | 不沿着连接轴保留索引,而产生一段新的索引(长度为两表长度之和) |
arr = np.arange(12).reshape((3, 4))
arr
np.concatenate([arr, arr], axis=1)
s1 = pd.Series([0, 1], index=['a', 'b'])
s2 = pd.Series([2, 3, 4], index=['c', 'd', 'e'])
s3 = pd.Series([5, 6], index=['f', 'g'])
# concatence默认沿轴0(即行)连接,采用 outer 外连接()即取键的并集
print(pd.concat([s1, s2, s3], axis=1), '\n')
s4 = pd.concat([s1, s3])
print(pd.concat([s1, s4], axis=1, join='inner'), '\n') # inner连接默认以所有左表的键为主
# 创建多层索引
result = pd.concat([s1, s1, s3], keys=['one', 'two', 'three'])
result
result.unstack() # 层级索引
df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=['a', 'b', 'c'],
columns=['one', 'two'])
df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=['a', 'c'],
columns=['three', 'four'])
df1
df2
print(pd.concat([df1, df2], axis=1, keys=['level1', 'level2']), '\n')
# 如果传递的对象是字典而不是列表,则字典的键会用于keys选项
pd.concat({'level1': df1, 'level2': df2}, axis=1)
pd.concat({'level1': df1, 'level2': df2}, axis=1)
# 参数names,用于多层索引的层级名称
names_concatLy = pd.concat([df1, df2], axis=1, keys=['level1', 'level2'],
names=['upper', 'lower'])
print(names_concatLy, '\n')
# 参数ignore_index,不沿着连接轴保留索引,而产生一段新的索引(长度为两表长度之和)
df1 = pd.DataFrame(np.random.randn(3, 4), columns=['a', 'b', 'c', 'd'])
df2 = pd.DataFrame(np.random.randn(2, 3), columns=['b', 'd', 'a'])
pd.concat([df1, df2], ignore_index=True)
④联合重叠数据
a = pd.Series([np.nan, 2.5, np.nan, 3.5, 4.5, np.nan],
index=['f', 'e', 'd', 'c', 'b', 'a'])
b = pd.Series(np.arange(len(a), dtype=np.float64),
index=['f', 'e', 'd', 'c', 'b', 'a'])
b[-1] = np.nan
print(a, '\n')
print(b, '\n')
print(np.where(pd.isnull(a), b, a), '\n')
# 传入对象以“填补”调用对象的缺失值
b.combine_first(a)
df1 = pd.DataFrame({'a': [1., np.nan, 5., np.nan],
'b': [np.nan, 2., np.nan, 6.],
'c': range(2, 18, 4)})
df2 = pd.DataFrame({'a': [5., 4., np.nan, 3., 7.],
'b': [np.nan, 3., 4., 6., 8.]})
print(df1, '\n')
print(df2, '\n')
# 传入对象以“填补”调用对象的缺失值
df1.combine_first(df2)
3.重塑和透视
①使用多层索引进行重塑
stack
(堆叠):会“旋转”或将列中的数据透视到行unstack
(拆堆):将行中的数据透视到列
data = pd.DataFrame(np.arange(6).reshape((2, 3)),
index=pd.Index(['Ohio', 'Colorado'], name='state'),
columns=pd.Index(['one', 'two', 'three'],
name='number'))
print(data)
# 透视到列
result = data.stack()
print(result, '\n')
# 透视到行
result.unstack('state') # 或:result.unstack(0)
s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'])
data2 = pd.concat([s1, s2], keys=['one', 'two'])
data2
print(data2.unstack(), '\n')
# 默认情况下,堆叠会过滤出缺失值,因此堆叠拆堆的操作是可逆的
print(data2.unstack().stack(), '\n')
# 取消过滤
data2.unstack().stack(dropna=False)
df = pd.DataFrame({'left': result, 'right': result + 5},
columns=pd.Index(['left', 'right'], name='side'))
df
# 当在DataFrame中拆堆时,被拆堆的层级会变为结果中最低的层级
print(df.unstack('state'), '\n')
df.unstack('state').stack('side')
②将“长”透视为“宽”
在数据库和CSV中存储多时间序列的方式就是所谓的长格式或堆叠格式。
pivot()
等价于 先用set_index
创建分层索引,然后调用unstack
data = pd.read_csv('examples/macrodata.csv')
print(data.head(), '\n')
periods = pd.PeriodIndex(year=data.year, quarter=data.quarter,
name='date')
columns = pd.Index(['realgdp', 'infl', 'unemp'], name='item')
data = data.reindex(columns=columns)
data.index = periods.to_timestamp('D', 'end')
ldata = data.stack().reset_index().rename(columns={0: 'value'})
ldata
pivoted = ldata.pivot('date', 'item')
pivoted[:5]
pivoted['value'][:5]
# pivot等价于先用 set_index 创建分层索引,然后调用unstack
unstacked = ldata.set_index(['date', 'item']).unstack('item')
unstacked[:7]
③将“宽”透视为“长”
pivot()
的反操作是pd.melt()
df = pd.DataFrame({'key': ['foo', 'bar', 'baz'],
'A': [1, 2, 3],
'B': [4, 5, 6],
'C': [7, 8, 9]})
df
melted = pd.melt(df, ['key'])
melted
reshaped = melted.pivot('key', 'variable', 'value')
print(reshaped, '\n')
# 将数据移回一列对齐
reshaped.reset_index()
# 选取指定列的子集作为值列
pd.melt(df, id_vars=['key'], value_vars=['A', 'B'])
# 可以无任何分组指标
pd.melt(df, value_vars=['A', 'B', 'C'])
pd.melt(df, value_vars=['key', 'A', 'B'])
七、数据聚合与分组操作
详见【资源】- 数据聚合与分组操作.ipynb文件