在许多应用中,数据可能分散在许多文件或数据库中,存储的形式也不利于分析。本部分关注可以聚合、合并、重塑数据的方法。
# 导入包
import pandas as pd
import numpy as np
pandas对象中的数据可以通过一些方式进行合并:
- 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)