在许多应用中,数据可能分散在许多文件或数据库中,存储的形式也不利于分析。本部分关注可以聚合、合并、重塑数据的方法。
# 导入包 import pandas as pd import numpy as np合并数据集
pandas对象中的数据可以通过一些方式进行合并:
数据库风格的DataFrame合并
- pandas.merge可根据一个或多个键将不同DataFrame中的行连接起来。其实现的就是数据库的join操作。
- pandas.concat可以沿着一条轴将多个对象堆叠到一起。
- 实例方法combine_first可以将重复数据拼接在一起,用一个对象中的值填充另一个对象中的缺失值。
df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
'data1': range(7)})
df2 = pd.DataFrame({'key': ['a', 'b', 'd'],
'data2': range(3)})
df1
| key | data1 | |
|---|---|---|
| 0 | b | 0 |
| 1 | b | 1 |
| 2 | a | 2 |
| 3 | c | 3 |
| 4 | a | 4 |
| 5 | a | 5 |
| 6 | b | 6 |
df2
| key | data2 | |
|---|---|---|
| 0 | a | 0 |
| 1 | b | 1 |
| 2 | d | 2 |
通过on=指定用哪个列进行连接:
pd.merge(df1, df2, on='key')
| key | data1 | data2 | |
|---|---|---|---|
| 0 | b | 0 | 1 |
| 1 | b | 1 | 1 |
| 2 | b | 6 | 1 |
| 3 | a | 2 | 0 |
| 4 | a | 4 | 0 |
| 5 | a | 5 | 0 |
如果两个对象的列名不同,可以分别指定:
df1 = pd.DataFrame({'key1': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
'data1': range(7)})
df2 = pd.DataFrame({'key2': ['a', 'b', 'd'],
'data2': range(3)})
pd.merge(df1, df2, left_on='key1', right_on = 'key2')
| key1 | data1 | key2 | data2 | |
|---|---|---|---|---|
| 0 | b | 0 | b | 1 |
| 1 | b | 1 | b | 1 |
| 2 | b | 6 | b | 1 |
| 3 | a | 2 | a | 0 |
| 4 | a | 4 | a | 0 |
| 5 | a | 5 | a | 0 |
默认情况下,merge做的是“内连接”;结果中的键是交集。其他方式还有"left"、“right"以及"outer”,通过`how='字段进行指定:
pd.merge(df1, df2, left_on='key1', right_on = 'key2',how = 'right')
| key1 | data1 | key2 | data2 | |
|---|---|---|---|---|
| 0 | a | 2.0 | a | 0 |
| 1 | a | 4.0 | a | 0 |
| 2 | a | 5.0 | a | 0 |
| 3 | b | 0.0 | b | 1 |
| 4 | b | 1.0 | b | 1 |
| 5 | b | 6.0 | b | 1 |
| 6 | NaN | NaN | d | 2 |
以下列出可选的四种连接方式:
| 选项 | 说明 |
|---|---|
| inner | 使用两个表都有的键 |
| left | 使用左表中所有的键 |
| right | 使用右表中所有的键 |
| outer | 使用两个表中所有的键 |
要根据多个键进行合并,传入一个由列名组成的列表即可:
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]})
pd.merge(left, right, on=['key1','key2'], how = 'outer')
| key1 | key2 | lval | rval | |
|---|---|---|---|---|
| 0 | foo | one | 1.0 | 4.0 |
| 1 | foo | one | 1.0 | 5.0 |
| 2 | foo | two | 2.0 | NaN |
| 3 | bar | one | 3.0 | 6.0 |
| 4 | bar | two | NaN | 7.0 |
待合并的对象若存在重复列名,可以通过suffixes选项指定附加到重复列名上的字符串:
pd.merge(left, right, on='key1',suffixes=('_left','_right'))
| key1 | key2_left | lval | key2_right | rval | |
|---|---|---|---|---|---|
| 0 | foo | one | 1 | one | 4 |
| 1 | foo | one | 1 | one | 5 |
| 2 | foo | two | 2 | one | 4 |
| 3 | foo | two | 2 | one | 5 |
| 4 | bar | one | 3 | one | 6 |
| 5 | bar | one | 3 | two | 7 |
以下为merge函数的参数:
上面的pd.merge方法通过right_index和left_index参数可以用于指定将右侧或左侧的行索引用作连接键,这里不做示例。此部分介绍一个更便捷的join方法,它能更方便地实现按索引合并:
left = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
index=['a', 'c', 'e'],
columns=['Ohio', 'Nevada'])
right = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
index=['b', 'c', 'd', 'e'],
columns=['Missouri', 'Alabama'])
left
| Ohio | Nevada | |
|---|---|---|
| a | 1.0 | 2.0 |
| c | 3.0 | 4.0 |
| e | 5.0 | 6.0 |
right
| Missouri | Alabama | |
|---|---|---|
| b | 7.0 | 8.0 |
| c | 9.0 | 10.0 |
| d | 11.0 | 12.0 |
| e | 13.0 | 14.0 |
因为一些历史版本的遗留原因,DataFrame的join方法默认使用的是左连接,保留左边表的行索引:
left.join(right)
| Ohio | Nevada | Missouri | Alabama | |
|---|---|---|---|---|
| a | 1.0 | 2.0 | NaN | NaN |
| c | 3.0 | 4.0 | 9.0 | 10.0 |
| e | 5.0 | 6.0 | 13.0 | 14.0 |
对于简单的索引合并,你还可以向join传入一组DataFrame:
another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],
index=['a', 'c', 'e', 'f'],
columns=['New York','Oregon'])
another
| New York | Oregon | |
|---|---|---|
| a | 7.0 | 8.0 |
| c | 9.0 | 10.0 |
| e | 11.0 | 12.0 |
| f | 16.0 | 17.0 |
join支持{‘left’, ‘right’, ‘outer’, ‘inner’}四种连接方式:
left.join([right,another], how='outer')
| Ohio | Nevada | Missouri | Alabama | New York | Oregon | |
|---|---|---|---|---|---|---|
| a | 1.0 | 2.0 | NaN | NaN | 7.0 | 8.0 |
| c | 3.0 | 4.0 | 9.0 | 10.0 | 9.0 | 10.0 |
| e | 5.0 | 6.0 | 13.0 | 14.0 | 11.0 | 12.0 |
| b | NaN | NaN | 7.0 | 8.0 | NaN | NaN |
| d | NaN | NaN | 11.0 | 12.0 | NaN | NaN |
| f | NaN | NaN | NaN | NaN | 16.0 | 17.0 |
假设有三个没有重叠索引的Series,pd.concat可以将值和索引堆叠在一起,默认在axis=0上工作:
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'])
pd.concat([s1,s2,s3])
a 0 b 1 c 2 d 3 e 4 f 5 g 6 dtype: int64
传入axis=1,在列的方向上进行连接,会变成一个DataFrame:
pd.concat([s1,s2,s3], axis=1)
| 0 | 1 | 2 | |
|---|---|---|---|
| a | 0.0 | NaN | NaN |
| b | 1.0 | NaN | NaN |
| c | NaN | 2.0 | NaN |
| d | NaN | 3.0 | NaN |
| e | NaN | 4.0 | NaN |
| f | NaN | NaN | 5.0 |
| g | NaN | NaN | 6.0 |
concat之后不知道堆叠的数据代表什么,可以通过keys参数设定连接的片段在结果中的索引:
#沿着axis=0对Series进行合并,生成层次化索引 pd.concat([s1,s2,s3], keys = ['s1','s2','s3'])
s1 a 0
b 1
s2 c 2
d 3
e 4
s3 f 5
g 6
dtype: int64
#如果沿着axis=1对Series进行合并,则keys就会成为DataFrame的列头: pd.concat([s1,s2,s3], axis=1, keys = ['s1','s2','s3'])
| s1 | s2 | s3 | |
|---|---|---|---|
| a | 0.0 | NaN | NaN |
| b | 1.0 | NaN | NaN |
| c | NaN | 2.0 | NaN |
| d | NaN | 3.0 | NaN |
| e | NaN | 4.0 | NaN |
| f | NaN | NaN | 5.0 |
| g | NaN | NaN | 6.0 |
如果索引存在重叠,可以通过join=参数控制连接方式为inner或outer(默认为’outer’):
s4 = pd.concat([s1, s3])
s1
a 0 b 1 dtype: int64
s4
a 0 b 1 f 5 g 6 dtype: int64
pd.concat([s1,s4], axis =1)
| 0 | 1 | |
|---|---|---|
| a | 0.0 | 0 |
| b | 1.0 | 1 |
| f | NaN | 5 |
| g | NaN | 6 |
pd.concat([s1,s4], axis =1, join='inner')
| 0 | 1 | |
|---|---|---|
| a | 0 | 0 |
| b | 1 | 1 |
上述Series同样的逻辑也适用于DataFrame对象。需说明的是,如果DataFrame的行索引不包含任何有意义的数据,可以通过传入ignore_index=True声明不保留连接轴上的索引,产生一组新索引range(total_length):
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'])
df1
| a | b | c | d | |
|---|---|---|---|---|
| 0 | 0.095987 | -1.429683 | 0.213154 | 0.209158 |
| 1 | 0.405074 | 2.060263 | 0.154940 | -1.547939 |
| 2 | -0.283798 | 0.185476 | -0.833157 | 0.204095 |
df2
| b | d | a | |
|---|---|---|---|
| 0 | -0.743573 | -0.081083 | -0.832971 |
| 1 | 0.432616 | -0.393941 | -1.465352 |
pd.concat([df1,df2], ignore_index = True)
| a | b | c | d | |
|---|---|---|---|---|
| 0 | 0.095987 | -1.429683 | 0.213154 | 0.209158 |
| 1 | 0.405074 | 2.060263 | 0.154940 | -1.547939 |
| 2 | -0.283798 | 0.185476 | -0.833157 | 0.204095 |
| 3 | -0.832971 | -0.743573 | NaN | -0.081083 |
| 4 | -1.465352 | 0.432616 | NaN | -0.393941 |
比如说,你可能有索引全部或部分重叠的两个数据集。你希望用一个数据集去填补另一个数据集中相应字段的缺失值:
s1 = pd.Series([np.nan, 2.5, np.nan, 3.5, 4.5, np.nan],
index=['f', 'e', 'd', 'c', 'b', 'a'])
s2 = pd.Series(np.arange(len(a), dtype=np.float64),
index=['f', 'e', 'd', 'c', 'b', 'a'])
s2[-1] = np.nan
s1
f NaN e 2.5 d NaN c 3.5 b 4.5 a NaN dtype: float64
s2
f 0.0 e 1.0 d 2.0 c 3.0 b 4.0 a NaN dtype: float64
方法一:np.where,表示一种等价于面向数组的if-else:
# 条件表达式成立即s1为空值的地方,取s2中值,否则保留s1中值 np.where(pd.isnull(s1), s2, s1)
array([0. , 2.5, 2. , 3.5, 4.5, nan])
方法二:fillna
s1.fillna(s2)
f 0.0 e 2.5 d 2.0 c 3.5 b 4.5 a NaN dtype: float64
方法三:combine_first
s1.combine_first(s2)
f 0.0 e 2.5 d 2.0 c 3.5 b 4.5 a NaN dtype: float64
以上方法对DataFrame同样适用,用传递对象中的数据为调用对象的缺失数据“打补丁”:
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.]})
df1
| a | b | c | |
|---|---|---|---|
| 0 | 1.0 | NaN | 2 |
| 1 | NaN | 2.0 | 6 |
| 2 | 5.0 | NaN | 10 |
| 3 | NaN | 6.0 | 14 |
df2
| a | b | |
|---|---|---|
| 0 | 5.0 | NaN |
| 1 | 4.0 | 3.0 |
| 2 | NaN | 4.0 |
| 3 | 3.0 | 6.0 |
| 4 | 7.0 | 8.0 |
df1.combine_first(df2)
| a | b | c | |
|---|---|---|---|
| 0 | 1.0 | NaN | 2.0 |
| 1 | 4.0 | 2.0 | 6.0 |
| 2 | 5.0 | 4.0 | 10.0 |
| 3 | 3.0 | 6.0 | 14.0 |
| 4 | 7.0 | 8.0 | NaN |
层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。
对于一个DataFrame,每条轴都可以有分层索引:
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']])
frame
| Ohio | Colorado | |||
|---|---|---|---|---|
| Green | Red | Green | ||
| a | 1 | 0 | 1 | 2 |
| 2 | 3 | 4 | 5 | |
| b | 1 | 6 | 7 | 8 |
| 2 | 9 | 10 | 11 | |
各层都可以有名字:
frame.index.names = ['key1','key2']
frame.columns.names = ['state','color']
frame
| state | Ohio | Colorado | ||
|---|---|---|---|---|
| color | Green | Red | Green | |
| key1 | key2 | |||
| a | 1 | 0 | 1 | 2 |
| 2 | 3 | 4 | 5 | |
| b | 1 | 6 | 7 | 8 |
| 2 | 9 | 10 | 11 | |
swaplevel接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化):
frame.swaplevel(0,1)
| state | Ohio | Colorado | ||
|---|---|---|---|---|
| color | Green | Red | Green | |
| key2 | key1 | |||
| 1 | a | 0 | 1 | 2 |
| 2 | a | 3 | 4 | 5 |
| 1 | b | 6 | 7 | 8 |
| 2 | b | 9 | 10 | 11 |
frame.swaplevel('key1','key2')
| state | Ohio | Colorado | ||
|---|---|---|---|---|
| color | Green | Red | Green | |
| key2 | key1 | |||
| 1 | a | 0 | 1 | 2 |
| 2 | a | 3 | 4 | 5 |
| 1 | b | 6 | 7 | 8 |
| 2 | b | 9 | 10 | 11 |
sort_index则根据单个级别中的值对数据进行排序。交换级别时,常会用到该命令。
frame.swaplevel('key1','key2').sort_index(level=0)
| state | Ohio | Colorado | ||
|---|---|---|---|---|
| color | Green | Red | Green | |
| key2 | key1 | |||
| 1 | a | 0 | 1 | 2 |
| b | 6 | 7 | 8 | |
| 2 | a | 3 | 4 | 5 |
| b | 9 | 10 | 11 | |
在一些数据分析的场景中,可能想要将DataFrame的一个或多个列当做行索引来用,或者可能希望将行索引变成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]})
frame
| a | b | c | d | |
|---|---|---|---|---|
| 0 | 0 | 7 | one | 0 |
| 1 | 1 | 6 | one | 1 |
| 2 | 2 | 5 | one | 2 |
| 3 | 3 | 4 | two | 0 |
| 4 | 4 | 3 | two | 1 |
| 5 | 5 | 2 | two | 2 |
| 6 | 6 | 1 | two | 3 |
DataFrame的set_index函数会将其一个或多个列转换为行索引,并创建一个新的DataFrame:
frame2 = frame.set_index(['c','d'])
frame2
| a | b | ||
|---|---|---|---|
| c | d | ||
| one | 0 | 0 | 7 |
| 1 | 1 | 6 | |
| 2 | 2 | 5 | |
| two | 0 | 3 | 4 |
| 1 | 4 | 3 | |
| 2 | 5 | 2 | |
| 3 | 6 | 1 |
reset_index的功能跟set_index刚好相反,层次化索引的级别会被转移到列里面:
frame2.reset_index()
| c | d | a | b | |
|---|---|---|---|---|
| 0 | one | 0 | 0 | 7 |
| 1 | one | 1 | 1 | 6 |
| 2 | one | 2 | 2 | 5 |
| 3 | two | 0 | 3 | 4 |
| 4 | two | 1 | 4 | 3 |
| 5 | two | 2 | 5 | 2 |
| 6 | two | 3 | 6 | 1 |
层次化索引为DataFrame数据的重排任务提供了一种具有良好一致性的方式。主要功能有二:
- stack:将数据的列“旋转”为行。
- unstack:将数据的行“旋转”为列。
比如,以下数据为a、b、c、d四名学生,1、2、3三门课程的考试成绩:
data = pd.DataFrame(list(zip(['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'],
[1, 2, 3, 1, 3, 1, 2, 2, 3],
np.random.randn(9))),
columns = ['student','course','score'])
data
| student | course | score | |
|---|---|---|---|
| 0 | a | 1 | 0.469069 |
| 1 | a | 2 | 0.568829 |
| 2 | a | 3 | 1.293216 |
| 3 | b | 1 | -0.799458 |
| 4 | b | 3 | 2.323059 |
| 5 | c | 1 | 0.419970 |
| 6 | c | 2 | 0.576399 |
| 7 | d | 2 | 0.459112 |
| 8 | d | 3 | -0.654535 |
层次化索引在数据重塑和基于分组的操作(如透视表生成)中扮演着重要的角色,例如:
首先,将"student"、"course"设置为index, 形成层次化索引:
data = data.set_index(['student','course'])
data
| score | ||
|---|---|---|
| student | course | |
| a | 1 | 0.469069 |
| 2 | 0.568829 | |
| 3 | 1.293216 | |
| b | 1 | -0.799458 |
| 3 | 2.323059 | |
| c | 1 | 0.419970 |
| 2 | 0.576399 | |
| d | 2 | 0.459112 |
| 3 | -0.654535 |
其次,通过unstack命令对数据进行重塑,得到学生和课程的透视表,这样便于后续对成绩之间进行一些统计运算:
result = data.unstack()
result
| score | |||
|---|---|---|---|
| course | 1 | 2 | 3 |
| student | |||
| a | 0.469069 | 0.568829 | 1.293216 |
| b | -0.799458 | NaN | 2.323059 |
| c | 0.419970 | 0.576399 | NaN |
| d | NaN | 0.459112 | -0.654535 |
stack为unstack的逆运算:
result.stack()
| score | ||
|---|---|---|
| student | course | |
| a | 1 | 0.469069 |
| 2 | 0.568829 | |
| 3 | 1.293216 | |
| b | 1 | -0.799458 |
| 3 | 2.323059 | |
| c | 1 | 0.419970 |
| 2 | 0.576399 | |
| d | 2 | 0.459112 |
| 3 | -0.654535 |
往期:
利用Python进行数据分析:准备工作
利用Python进行数据分析:缺失数据(基于DataFrame)
利用Python进行数据分析:数据转换(基于DataFrame)



